pax_global_header00006660000000000000000000000064117161001250014504gustar00rootroot0000000000000052 comment=01e789a1e168890c7df592ddf239d95e194661cd sitaramc-gitolite-6857652/000077500000000000000000000000001171610012500153355ustar00rootroot00000000000000sitaramc-gitolite-6857652/.gitattributes000066400000000000000000000002141171610012500202250ustar00rootroot00000000000000conf/* crlf=input src/* crlf=input hooks/common/* crlf=input hooks/gitolite-admin/* crlf=input sitaramc-gitolite-6857652/.gitignore000066400000000000000000000000541171610012500173240ustar00rootroot00000000000000*.tar *.tgz *.tar.gz *.tar.bz2 conf/VERSION sitaramc-gitolite-6857652/Makefile000066400000000000000000000011441171610012500167750ustar00rootroot00000000000000# this is a simple wrapper around "git archive" using make # "make [refname].tar" produces a tar of refname, then adds a file containing # the "git describe" output for that refname to the tar. This lets you say # "cat .GITOLITE-VERSION" to find out which ref produced this tar # Note: I'm not sure if that "-r" is a GNU tar extension... branch := $(shell git rev-parse --abbrev-ref HEAD) $(branch): $(branch).tar .GITOLITE-VERSION: @touch conf/VERSION %.tar: .GITOLITE-VERSION git describe --tags --long $* > conf/VERSION git archive $* > $@ tar -r -f $@ conf/VERSION rm conf/VERSION cp -v $@ /tmp sitaramc-gitolite-6857652/README.mkd000066400000000000000000000015101171610012500167640ustar00rootroot00000000000000# Gitolite README **Github users: please read the "wiki" link at the top of the page before submitting issues or pull requests**. If you're really impatient, and you're familiar with Unix and ssh, follow the [quick install](http://sitaramc.github.com/gitolite/index.html#qi) instructions. But if you want to do anything meaningful with gitolite you have to spend some time cuddling up to the docs. **The complete online documentation starts [here](http://sitaramc.github.com/gitolite)** -- this is the best starting point for general questions about git, such as what it is, why you would need it, features, contact/mailing list info, and so on. For convenience, here is a link to the [master table of contents](http://sitaramc.github.com/gitolite/master-toc.html), which is very useful to search using your browser's search function. sitaramc-gitolite-6857652/conf/000077500000000000000000000000001171610012500162625ustar00rootroot00000000000000sitaramc-gitolite-6857652/conf/example.conf000066400000000000000000000002251171610012500205630ustar00rootroot00000000000000# see doc/gitolite.conf.mkd for help on the syntax and semantics of this file # online at http://sitaramc.github.com/gitolite/doc/gitolite.conf.html sitaramc-gitolite-6857652/conf/example.gitolite.rc000066400000000000000000000101141171610012500220570ustar00rootroot00000000000000# configuration variables for gitolite # PLEASE READ THE DOCUMENTATION BEFORE EDITING OR ASKING QUESTIONS # ( http://github.com/sitaramc/gitolite/blob/pu/doc/gitolite.rc.mkd ) # ( or http://sitaramc.github.com/gitolite/doc/gitolite.rc.html ) # 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 # ------------------------------------------------------------------------------ # 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 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 = ""; # ------------------------------------------------------------------------------ # most often used/changed variables # ------------------------------------------------------------------------------ $GL_WILDREPOS = 0; $PROJECTS_LIST = $ENV{HOME} . "/projects.list"; # $WEB_INTERFACE = "gitweb"; # $GITWEB_URI_ESCAPE = 0; $REPO_UMASK = 0077; # ------------------------------------------------------------------------------ # variables with an efficiency/performance impact # ------------------------------------------------------------------------------ $GL_BIG_CONFIG = 0; $GL_NO_DAEMON_NO_GITWEB = 0; # $GL_NICE_VALUE = 0; # $BIG_INFO_CAP = 20; # ------------------------------------------------------------------------------ # 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 # (or http://sitaramc.github.com/gitolite/doc/gitolite.rc.html#_variables_with_a_security_impact) # ------------------------------------------------------------------------------ # $GL_ALL_READ_ALL = 0; $GIT_PATH=""; $GL_GITCONFIG_KEYS = ""; $GL_NO_CREATE_REPOS = 0; $GL_NO_SETUP_AUTHKEYS = 0; # $GL_WILDREPOS_DEFPERMS = 'R @all'; $HTPASSWD_FILE = ""; $RSYNC_BASE = ""; $SVNSERVE = ""; # $UPDATE_CHAINS_TO = "hooks/update.secondary"; # $ADMIN_POST_UPDATE_CHAINS_TO = "hooks/post-update.secondary"; # $GL_ADC_PATH = ""; # $GL_GET_MEMBERSHIPS_PGM = "/usr/local/bin/expand-ldap-user-to-groups" # $GL_HTTP_ANON_USER = "mob"; # $GL_REF_OR_FILENAME_PATT=qr(^[0-9a-zA-Z][0-9a-zA-Z._\@/+ :,-]*$); # ------------------------------------------------------------------------------ # less used/changed variables # ------------------------------------------------------------------------------ # $GL_ALL_INCLUDES_SPECIAL = 0; # $GL_SLAVE_MODE = 0; # $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"; # $GL_HOSTNAME = "frodo"; # read doc/mirroring.mkd COMPLETELY before setting this # ------------------------------------------------------------------------------ # rarely changed variables # ------------------------------------------------------------------------------ $GL_LOGT="$GL_ADMINDIR/logs/gitolite-%y-%m.log"; # $GL_PERFLOGT="$GL_ADMINDIR/logs/perf-gitolite-%y-%m.log"; # ------------------------------------------------------------------------------ # 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; # Local variables: # mode: perl # End: # vim: set syn=perl: sitaramc-gitolite-6857652/contrib/000077500000000000000000000000001171610012500167755ustar00rootroot00000000000000sitaramc-gitolite-6857652/contrib/adc/000077500000000000000000000000001171610012500175245ustar00rootroot00000000000000sitaramc-gitolite-6857652/contrib/adc/README-adc.mkd000066400000000000000000000043001171610012500217000ustar00rootroot00000000000000# 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**:
**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. sitaramc-gitolite-6857652/contrib/adc/able000077500000000000000000000022501171610012500203540ustar00rootroot00000000000000#!/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 sitaramc-gitolite-6857652/contrib/adc/adc.common-functions000066400000000000000000000074471171610012500235070ustar00rootroot00000000000000#!/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 } sitaramc-gitolite-6857652/contrib/adc/delete-branch000077500000000000000000000060461171610012500221550ustar00rootroot00000000000000#!/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 () { ($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; sitaramc-gitolite-6857652/contrib/adc/fork000077500000000000000000000020601171610012500204110ustar00rootroot00000000000000#!/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 cp -R $GL_REPO_BASE_ABS/$from.git/hooks/* $GL_REPO_BASE_ABS/$to.git/hooks 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 sitaramc-gitolite-6857652/contrib/adc/get-rights-and-owner.in-perl000077500000000000000000000034051171610012500247660ustar00rootroot00000000000000#!/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 "" for a normal repo, an actual username for # a wildrepo, or "" 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). sitaramc-gitolite-6857652/contrib/adc/getdesc000066400000000000000000000010421171610012500210620ustar00rootroot00000000000000#!/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"; sitaramc-gitolite-6857652/contrib/adc/git000077500000000000000000000105251171610012500202400ustar00rootroot00000000000000#!/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 '') # 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; sitaramc-gitolite-6857652/contrib/adc/git-annex-shell000077500000000000000000000040611171610012500224520ustar00rootroot00000000000000#!/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; sitaramc-gitolite-6857652/contrib/adc/gl-reflog000077500000000000000000000061221171610012500213310ustar00rootroot00000000000000#!/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"); } sitaramc-gitolite-6857652/contrib/adc/help000077500000000000000000000035051171610012500204050ustar00rootroot00000000000000#!/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 cat <; $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; sitaramc-gitolite-6857652/contrib/adc/hub000077500000000000000000000354531171610012500202420ustar00rootroot00000000000000#!/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 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 view-diff parent request-number 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 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 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; } } sitaramc-gitolite-6857652/contrib/adc/hub.mkd000066400000000000000000000203551171610012500210040ustar00rootroot00000000000000# 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 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 ### 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 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 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 ;-) sitaramc-gitolite-6857652/contrib/adc/list-trash000077500000000000000000000004541171610012500215470ustar00rootroot00000000000000#!/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- sitaramc-gitolite-6857652/contrib/adc/lock000077500000000000000000000004601171610012500204020ustar00rootroot00000000000000#!/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." sitaramc-gitolite-6857652/contrib/adc/perms000077500000000000000000000067671171610012500206200ustar00rootroot00000000000000#!/usr/bin/env python # # Original author: Richard Bateman # # 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 " % (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 " % (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 " print " list [TYPE]" print " clear " print " add [user and group list]" print " set [user and group list]" print " remove [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:]) sitaramc-gitolite-6857652/contrib/adc/pygitolite.py000066400000000000000000000054121171610012500222710ustar00rootroot00000000000000#!/usr/bin/env python # # Original author: Richard Bateman # # 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) sitaramc-gitolite-6857652/contrib/adc/repo-deletion.mkd000066400000000000000000000042651171610012500227760ustar00rootroot00000000000000# 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. sitaramc-gitolite-6857652/contrib/adc/restore000077500000000000000000000006111171610012500211330ustar00rootroot00000000000000#!/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 sitaramc-gitolite-6857652/contrib/adc/restrict-admin000077500000000000000000000003411171610012500223750ustar00rootroot00000000000000#!/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! sitaramc-gitolite-6857652/contrib/adc/rm000077500000000000000000000020311171610012500200640ustar00rootroot00000000000000#!/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 sitaramc-gitolite-6857652/contrib/adc/rmrepo000077500000000000000000000001441171610012500207550ustar00rootroot00000000000000#!/bin/bash echo "this command no longer exists. Please run the 'help' adc for more info." exit 1 sitaramc-gitolite-6857652/contrib/adc/rsync000077500000000000000000000056531171610012500206210ustar00rootroot00000000000000#!/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 ;-) sitaramc-gitolite-6857652/contrib/adc/s3backup000066400000000000000000000075001171610012500211640ustar00rootroot00000000000000#!/usr/bin/perl # Copyright 2011, David Bremner # # 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; sitaramc-gitolite-6857652/contrib/adc/setdesc000066400000000000000000000011141171610012500210760ustar00rootroot00000000000000#!/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"); sitaramc-gitolite-6857652/contrib/adc/sskm000077500000000000000000000225501171610012500204330ustar00rootroot00000000000000#!/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; local(*STDERR) = \*STDERR; open(STDOUT, ">", "/dev/null"); open(STDERR, ">", "/dev/null"); system("git", @_); } sub highlander { # there can be only one my($keyid, $die_if_empty, @a) = @_; # too many? if (@a > 1) { print STDERR " more than one key satisfies this condition, and I can't deal with that! The keys are: "; print STDERR "\t" . join("\n\t", @a), "\n\n"; exit 1; } # too few? die "no keys with " . ($keyid || "empty") . " keyid found\n" if $die_if_empty and not @a; return @a; } sub kf_add { my($gl_user, $keyid, $keymaterial) = @_; # add a new "marked for addition" key for $gl_user. cd_temp_clone(); chdir("keydir"); mkdir("zzz-marked"); wrap_print("zzz-marked/zzz-marked-for-add-$gl_user$keyid.pub", $keymaterial); hushed_git("add", ".") and die "git add failed\n"; my $fp = fingerprint("zzz-marked/zzz-marked-for-add-$gl_user$keyid.pub"); hushed_git("commit", "-m", "sskm: add $gl_user$keyid ($fp)") and die "git commit failed\n"; system("env GL_BYPASS_UPDATE_HOOK=1 git push >/dev/null 2>/dev/null") and die "git push failed\n"; } sub kf_confirm_add { my($gl_user, $keyid) = @_; # find entries in both @pubkeys and @marked_for_add whose basename matches $gl_user$keyid my @pk = highlander($keyid, 0, grep { m(^(.*/)?$gl_user$keyid.pub$) } @pubkeys); my @mfa = highlander($keyid, 1, grep { m(^zzz-marked/zzz-marked-for-add-$gl_user$keyid.pub$) } @marked_for_add); cd_temp_clone(); chdir("keydir"); my $fp = fingerprint($mfa[0]); if ($pk[0]) { hushed_git("mv", "-f", $mfa[0], $pk[0]); hushed_git("commit", "-m", "sskm: confirm-add (replace) $pk[0] ($fp)") and die "git commit failed\n"; } else { hushed_git("mv", "-f", $mfa[0], "$gl_user$keyid.pub"); hushed_git("commit", "-m", "sskm: confirm-add $gl_user$keyid ($fp)") and die "git commit failed\n"; } system("env GL_BYPASS_UPDATE_HOOK=1 git push >/dev/null 2>/dev/null") and die "git push failed\n"; } sub kf_undo_add { # XXX some code at start is shared with kf_confirm_add my($gl_user, $keyid) = @_; my @mfa = highlander($keyid, 1, grep { m(^zzz-marked/zzz-marked-for-add-$gl_user$keyid.pub$) } @marked_for_add); cd_temp_clone(); chdir("keydir"); my $fp = fingerprint($mfa[0]); hushed_git("rm", $mfa[0]); hushed_git("commit", "-m", "sskm: undo-add $gl_user$keyid ($fp)") and die "git commit failed\n"; system("env GL_BYPASS_UPDATE_HOOK=1 git push >/dev/null 2>/dev/null") and die "git push failed\n"; } sub kf_del { my($gl_user, $keyid) = @_; cd_temp_clone(); chdir("keydir"); mkdir("zzz-marked"); my @pk = highlander($keyid, 1, grep { m(^(.*/)?$gl_user$keyid.pub$) } @pubkeys); my $fp = fingerprint($pk[0]); hushed_git("mv", $pk[0], "zzz-marked/zzz-marked-for-del-$gl_user$keyid.pub") and die "git mv failed\n"; hushed_git("commit", "-m", "sskm: del $pk[0] ($fp)") and die "git commit failed\n"; system("env GL_BYPASS_UPDATE_HOOK=1 git push >/dev/null 2>/dev/null") and die "git push failed\n"; } sub kf_confirm_del { my($gl_user, $keyid) = @_; my @mfd = highlander($keyid, 1, grep { m(^zzz-marked/zzz-marked-for-del-$gl_user$keyid.pub$) } @marked_for_del); cd_temp_clone(); chdir("keydir"); my $fp = fingerprint($mfd[0]); hushed_git("rm", $mfd[0]); hushed_git("commit", "-m", "sskm: confirm-del $gl_user$keyid ($fp)") and die "git commit failed\n"; system("env GL_BYPASS_UPDATE_HOOK=1 git push >/dev/null 2>/dev/null") and die "git push failed\n"; } sub kf_undo_del { my ($gl_user, $keyid) = @_; my @mfd = highlander($keyid, 1, grep { m(^zzz-marked/zzz-marked-for-del-$gl_user$keyid.pub$) } @marked_for_del); print STDERR " You're undeleting a key that is currently marked for deletion. Hit ENTER to undelete this key Hit Ctrl-C to cancel the undelete Please see documentation for caveats on the undelete process as well as how to actually delete it. "; <>; # yeay... always wanted to do that -- throw away user input! cd_temp_clone(); chdir("keydir"); my $fp = fingerprint($mfd[0]); hushed_git("mv", "-f", $mfd[0], "$gl_user$keyid.pub" ); hushed_git("commit", "-m", "sskm: undo-del $gl_user$keyid ($fp)") and die "git commit failed\n"; system("env GL_BYPASS_UPDATE_HOOK=1 git push >/dev/null 2>/dev/null") and die "git push failed\n"; } sitaramc-gitolite-6857652/contrib/adc/sskm.mkd000066400000000000000000000312121171610012500211750ustar00rootroot00000000000000# F=sskm changing keys -- self service key management Follow this guide to add keys to or remove keys from your account. Note that you cannot use this method to add your *first* key to the account; you must still email your initial key to your admin. The key management is done using an ADC (admin-defined command) called `sskm`. ---- ## Important! There are a few things that you should know before using the key management system. Please do not ignore this section! ### Key fingerprints Keys are identified in some of these subcommands by their fingerprints. To see the fingerprint for a public key on your computer, use the following syntax: ssh-keygen -l -f You'll get output like: jeff@baklava ~ $ ssh-keygen -l -f .ssh/jeffskey.pub 2048 2d:78:d4:2c:b1:6d:9a:dc:d9:0d:94:3c:d8:c2:65:44 .ssh/jeffskey.pub (RSA) ### Active keys Any keys that you can use to interact with the system are active keys. (Inactive keys are keys that are, for instance, scheduled to be added or removed.) Keys are identified with their `keyid`; see the section below on listing keys. If you have no current active keys, you will be locked out of the system (in which case email your admin for help). Therefore, be sure that you are never removing your only active key! ### Selecting which key to use Although you can identify yourself to the Gitolite system with any of your active keys on the server, at times it is necessary to specifically pick which key you are identifying with. To pick the key to use, pass the `-i` flag into `ssh`: jeff@baklava ~ $ ssh -i .ssh/jeffskey git@git info hello jeff, the gitolite version here is v2.0.1-11-g1cd3414 the gitolite config gives you the following access: @C R W [a-zA-Z0-9][a-zA-Z0-9_\-\.]+[a-zA-Z0-9] .... *N.B.*: If you have any keys loaded into `ssh-agent` (i.e., `ssh-add -l` shows at least one key), then this may not work properly. `ssh` has a bug which makes it ignore `-i` values when that key has not been loaded into the agent. One solution is to add the key you want to use (e.g., `ssh-add .ssh/jeffskey`). The other is to remove *all* the keys from the agent or disable the agent, using one of these commands: * Terminate `ssh-agent` or use `ssh-add -D` flag to remove identities from it * If using `keychain`, run `keychain --clear` to remove identities * Unset the `SSH_AUTH_SOCK` and `SSH_AGENT_PID` variables in the current shell ### Public vs. private keys In this guide, all keys are using their full suffix. In other words, if you see a `.pub` at the end of a key, it's the public key; if you don't, it's the private key. For instance, when using the `-i` flag with `ssh`, you are specifying private keys to use. When you are submitting a key for addition to the system, you are using the public key. ## Listing your existing keys To see a list of your existing keys, use the `list` argument to `sskm`: jeff@baklava ~ $ ssh git@git sskm list hello jeff, you are currently using a normal ("active") key you have the following keys: == active keys == 1: 72:ef:a3:e0:f5:06:f8:aa:6f:a2:88:9d:50:86:25:4e : jeff@key1.pub 2: 61:38:a7:9f:ba:cb:99:81:4f:49:2c:8b:c8:63:8e:33 : jeff@key2.pub 3: 2d:78:d4:2c:b1:6d:9a:dc:d9:0d:94:3c:d8:c2:65:44 : jeff@key3.pub Notice the `@` sign in each key's name? That sign and the text after that up until the `.pub` is the `keyid`. This is what you will use when identifying keys to the system. Above, for instance, one of my keys has the `keyid` of `@key3`. A keyid may be *empty*; in fact to start with you may only have a single `jeff.pub` key, depending on how your admin added your initial key. You can use any keyid you wish when adding keys (like `@home`, `@laptop`, ...); the only rules are that it must start with the `@` character and after that contain only digits, letters, or underscores. ## Adding or Replacing a key ### Step 1: Adding the Key Adding and replacing a key is the same process. What matters is the `keyid`. When adding a new key, use a new `keyid`; when replacing a key, pass in the `keyid` of the key you want to replace, as found by using the `list` subcommand. Pretty simple! To add a key, pipe in the text of your new key using `cat` to the `add` subcommand. In the example below, I explicitly select which existing, active pubkey to identify with for the command (using the `-i` parameter to ssh) for clarity: jeff@baklava ~ $ cat .ssh/newkey.pub | ssh -i .ssh/jeffskey git@git sskm add @key4 hello jeff, you are currently using a normal ("active") key please supply the new key on STDIN. (I recommend you don't try to do this interactively, but use a pipe) If you now run the `list` command you'll see that it's scheduled for addition: jeff@baklava ~ $ ssh -i .ssh/jeffskey git@git sskm list hello jeff, you are currently using a normal ("active") key you have the following keys: == active keys == 1: 72:ef:a3:e0:f5:06:f8:aa:6f:a2:88:9d:50:86:25:4e : jeff@key1.pub 2: 61:38:a7:9f:ba:cb:99:81:4f:49:2c:8b:c8:63:8e:33 : jeff@key2.pub 3: 2d:78:d4:2c:b1:6d:9a:dc:d9:0d:94:3c:d8:c2:65:44 : jeff@key3.pub == keys marked for addition/replacement == 1: ff:92:a2:20:6d:42:6b:cf:20:e8:a2:4a:3b:b0:32:3a : jeff@key4.pub ### Step 2: Confirming the addition Gitolite uses Git internally to store the keys. Just like with Git, where you commit locally before `push`-ing up to the server, you need to confirm the key addition (see the next section if you made a mistake). We use the `confirm-add` subcommand to do this, *but*: to verify that you truly have ownership of the corresponding private key, you *must* use the key you are adding itself to do the confirmation! (Inconvenient like most security, but very necessary from a security perspective.) This is where using the `-i` flag of `ssh` comes in handy: jeff@baklava ~ $ ssh -i .ssh/newkey git@git sskm confirm-add @key4 hello jeff, you are currently using a key in the 'marked for add' state Listing keys again shows that all four keys are now active: jeff@baklava ~ $ ssh -i .ssh/newkey git@git sskm list hello jeff, you are currently using a normal ("active") key you have the following keys: == active keys == 1: 72:ef:a3:e0:f5:06:f8:aa:6f:a2:88:9d:50:86:25:4e : jeff@key1.pub 2: 61:38:a7:9f:ba:cb:99:81:4f:49:2c:8b:c8:63:8e:33 : jeff@key2.pub 3: 2d:78:d4:2c:b1:6d:9a:dc:d9:0d:94:3c:d8:c2:65:44 : jeff@key3.pub 4: ff:92:a2:20:6d:42:6b:cf:20:e8:a2:4a:3b:b0:32:3a : jeff@key4.pub ### Optional: Undoing a mistaken add (before confirmation) Another advantage of Gitolite using Git internally is that that if we mistakenly add the wrong key, we can undo it before it's confirmed by passing in the `keyid` we want to remove into the `undo-add` subcommand: jeff@baklava ~ $ ssh -i .ssh/jeffskey git@git sskm undo-add @key4 hello jeff, you are currently using a normal ("active") key Listing the keys shows that that new key has been removed: jeff@baklava ~ $ ssh -i .ssh/jeffskey git@git sskm list hello jeff, you are currently using a normal ("active") key you have the following keys: == active keys == 1: 72:ef:a3:e0:f5:06:f8:aa:6f:a2:88:9d:50:86:25:4e : jeff@key1.pub 2: 61:38:a7:9f:ba:cb:99:81:4f:49:2c:8b:c8:63:8e:33 : jeff@key2.pub 3: 2d:78:d4:2c:b1:6d:9a:dc:d9:0d:94:3c:d8:c2:65:44 : jeff@key3.pub ## Removing a key ### Step 1: Mark the key for deletion Deleting a key works very similarly to adding a key, with `del` substituted for `add`. Let's say that I have my four keys from the example above: jeff@baklava ~ $ ssh -i .ssh/newkey git@git sskm list hello jeff, you are currently using a normal ("active") key you have the following keys: == active keys == 1: 72:ef:a3:e0:f5:06:f8:aa:6f:a2:88:9d:50:86:25:4e : jeff@key1.pub 2: 61:38:a7:9f:ba:cb:99:81:4f:49:2c:8b:c8:63:8e:33 : jeff@key2.pub 3: 2d:78:d4:2c:b1:6d:9a:dc:d9:0d:94:3c:d8:c2:65:44 : jeff@key3.pub 4: ff:92:a2:20:6d:42:6b:cf:20:e8:a2:4a:3b:b0:32:3a : jeff@key4.pub I would like to remove the key that on my box is called `newkey` and in the Gitolite system is known as `@key4`. I simply pass in the identifier to the `del` subcommand of `sskm`: jeff@baklava ~ $ ssh -i .ssh/newkey git@git sskm del @key4 hello jeff, you are currently using a normal ("active") key Listing the keys now shows that it is marked for deletion: jeff@baklava ~ $ ssh -i .ssh/newkey git@git sskm list hello jeff, you are currently using a key in the 'marked for del' state you have the following keys: == active keys == 1: 72:ef:a3:e0:f5:06:f8:aa:6f:a2:88:9d:50:86:25:4e : jeff@key1.pub 2: 61:38:a7:9f:ba:cb:99:81:4f:49:2c:8b:c8:63:8e:33 : jeff@key2.pub 3: 2d:78:d4:2c:b1:6d:9a:dc:d9:0d:94:3c:d8:c2:65:44 : jeff@key3.pub == keys marked for deletion == 1: ff:92:a2:20:6d:42:6b:cf:20:e8:a2:4a:3b:b0:32:3a : jeff@key4.pub ### Step 2: Confirming the deletion Just like with Git, where you commit locally before `push`-ing up to the server, you need to confirm the key addition (see the next section if you made a mistake). We use the `confirm-del` subcommand to do this, *but*: unlike the `confirm-add` subcommand, you *must* use a *different* key than the key you are deleting to do the confirmation! This prevents you from accidentally locking yourself out of the system by removing all active keys: jeff@baklava ~ $ ssh -i .ssh/jeffskey git@git sskm confirm-del @key4 hello jeff, you are currently using a normal ("active") key Listing keys again shows that the fourth key has been removed: jeff@baklava ~ $ ssh -i .ssh/jeffskey git@git sskm list hello jeff, you are currently using a normal ("active") key you have the following keys: == active keys == 1: 72:ef:a3:e0:f5:06:f8:aa:6f:a2:88:9d:50:86:25:4e : jeff@key1.pub 2: 61:38:a7:9f:ba:cb:99:81:4f:49:2c:8b:c8:63:8e:33 : jeff@key2.pub 3: 2d:78:d4:2c:b1:6d:9a:dc:d9:0d:94:3c:d8:c2:65:44 : jeff@key3.pub ### Optional: Undoing a mistaken delete (before confirmation) Another advantage of Gitolite using Git internally is that that if we mistakenly delete the wrong key, we can undo it before it's confirmed by passing in the `keyid` we want to keep into the `undo-del` subcommand. Note that this operation *must* be performed using the private key that corresponds to the key you are trying to keep! (Security reasons, similar to the reason that you must confirm an addition this way; it prevents anyone from undoing a deletion, and therefore keeping in the system, a key that they cannot prove (by having the corresponding private key) should stay in the system): jeff@baklava ~ $ ssh -i .ssh/newkey git@git sskm undo-del @key4 hello jeff, you are currently using a key in the 'marked for del' state You're undeleting a key that is currently marked for deletion. Hit ENTER to undelete this key Hit Ctrl-C to cancel the undelete Please see documentation for caveats on the undelete process as well as how to actually delete it. (Go ahead and hit ENTER there; the caveats are really only on the administrative side of things.) Listing the keys shows that that new key is now marked active again: jeff@baklava ~ $ ssh -i .ssh/newkey git@git sskm list hello jeff, you are currently using a normal ("active") key you have the following keys: == active keys == 1: 72:ef:a3:e0:f5:06:f8:aa:6f:a2:88:9d:50:86:25:4e : jeff@key1.pub 2: 61:38:a7:9f:ba:cb:99:81:4f:49:2c:8b:c8:63:8e:33 : jeff@key2.pub 3: 2d:78:d4:2c:b1:6d:9a:dc:d9:0d:94:3c:d8:c2:65:44 : jeff@key3.pub 4: ff:92:a2:20:6d:42:6b:cf:20:e8:a2:4a:3b:b0:32:3a : jeff@key4.pub ---- ## important notes for the admin These are the things that can break if you enable this ADC for your users: * if you, as the gitolite admin, are in the habit of force-pushing changes to the admin repo instead of doing a `git pull` (or, even better, a `git pull --rebase`) then you had better not enable this ADC. Your users will eventually come after you with pitchforks ;-) * there is no way to distinguish `foo/alice.pub` from `bar/alice.pub` using this ADC. You can distinguish `foo/alice.pub` from `bar/alice@home.pub`, but that's not because of the foo and bar, it's because the two files have different keyids. So, if you have the same *filename* in different subdirectories of `keydir`, you can't use this tool. * keys placed in specific folders (perhaps to do [this][authkeyopt], or for whatever other reasons), will probably not stay in those folders if this ADC is used. Even a key delete, followed by undoing the delete, will cause the key to effectively move to the root of the key store (i.e., the `keydir` directory in the gitolite-admin repo). sitaramc-gitolite-6857652/contrib/adc/su-expand000077500000000000000000000032631171610012500213620ustar00rootroot00000000000000#!/bin/sh # adc for someone with admin privs to invoke "expand" on other users. # This doc block as "WHY", "HOW", and "CAVEATS" sections; I mention that only # for those people with very small xterms who may miss the CAVEATS section # otherwise...! # WHY # === # ...because info has it, and expand doesn't :-) # (Possible question: Why not add that capability to expand instead to be # consistent? Probable answer: how about we go the other way and make # "info" also work like this, and remove that code from core? I like # having less less code in core!) # HOW # === # ssh gitolite su-expand . user1 user2 user3 # note that the first argument is still treated as a "pattern", so at # least put in a "." for a catch-all pattern. Also see caveats for # patterns below. # This will output the same thing as "ssh git@server expand ." performed # by the individual users # CAVEATS # ======= # (1) LIMITS TO THE PATTERN # Due to this being an ADC, and ADC arguments being very, Very, VERY, # stricly limited, you won't be able to use any regex meta characters # expect ".". I'll worry about changing it if enough people complain. # (2) NAME OF THIS SCRIPT # please name this script something other than "expand", because we can't # have name clashes between internal commands (info, expand, # (get|set)(perms|desc), etc) and admin-defined (external) commands. I'm # calling it su-expand; feel free to change it. . $(dirname $0)/adc.common-functions is_admin || die "just *what* are you trying to pull here, $GL_USER?" pat="$1"; shift for user do SSH_ORIGINAL_COMMAND="expand $pat" $GL_BINDIR/gl-auth-command $user done sitaramc-gitolite-6857652/contrib/adc/su-getperms000077500000000000000000000020261171610012500217250ustar00rootroot00000000000000#!/bin/sh # adc for someone with admin privs to invoke "setperms/getperms" on other # users. # copy this file under both names (su-setperms and su-getperms). It'll figure # out from the name what to do, since the code is almost the same for both # cases. # usage # ssh gitolite su-setperms user1 reponame # expects STDIN, like setperms # ssh gitolite su-getperms user1 reponame # prints existing perms, like getperms # WARNING: DO NOT NAME THIS SCRIPT "setperms" or "getperms". It won't work. . $(dirname $0)/adc.common-functions is_admin || die "just *what* are you trying to pull here, $GL_USER?" # find the command name; we don't do a lot of fancy checking -- we just go # "safe" and assume that anything but a name of "su-setperms" runs getperms cmd=getperms echo $0 | grep su-setperms$ >/dev/null 2>&1 && cmd=setperms # user and repo user="$1"; shift repo="$1"; shift [ -z "$repo" ] && die requires two parameters: a username and a reponame SSH_ORIGINAL_COMMAND="$cmd $repo" $GL_BINDIR/gl-auth-command $user sitaramc-gitolite-6857652/contrib/adc/su-setperms000077700000000000000000000000001171610012500241272su-getpermsustar00rootroot00000000000000sitaramc-gitolite-6857652/contrib/adc/sudo000077500000000000000000000010711171610012500204230ustar00rootroot00000000000000#!/bin/sh # this command is pretty cool, even if I may say so myself :) # for any ADC that a normal user can run, like # ssh git@server adc arguments # this adc lets a "super user" (defined as "have write access to the # gitolite-admin repo"), do this # ssh git@server sudo normal_user adc arguments . $(dirname $0)/adc.common-functions is_admin || die "just *what* are you trying to pull, young man?" user="$1"; shift cmd="$1"; shift GL_USER=$user; export GL_USER [ -x $(dirname $0)/$cmd ] || die "no adc called $cmd" exec $(dirname $0)/$cmd "$@" sitaramc-gitolite-6857652/contrib/adc/svnserve000066400000000000000000000006501171610012500213230ustar00rootroot00000000000000#!/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; my $cmd = $ENV{SSH_ORIGINAL_COMMAND}; die "expecting 'svnserve -t', got '$cmd'\n" unless $cmd eq 'svnserve -t'; $SVNSERVE =~ s/%u/$ENV{GL_USER}/g; exec $SVNSERVE; die "svnserve exec failed\n"; sitaramc-gitolite-6857652/contrib/adc/symbolic-ref000077500000000000000000000023161171610012500220470ustar00rootroot00000000000000#!/bin/sh # allow 'git symbolic-ref' over a gitolite connection # replaces and obsoletes the 'set-head' ADC, which was too specific # Security: remember all arguments to ADCs must match a very conservative # pattern; see doc/admin-defined-commands.mkd. Once that is assured, the # symbolic-ref command has no security related side-effects, so we don't check # arguments at all -- we just pass them on. # Note: because of the restriction on allowed characters in arguments, you # can't supply an arbitrary string to the '-m' option. The simplest # work-around is-to-just-use-join-up-words-like-this if you feel the need to # supply a "reason" string. In any case this is useless by default; you'd # have to have core.logAllRefUpdates set for it to have any meaning. . $(dirname $0)/adc.common-functions [ -z "$GL_RC" ] && die "ENV GL_RC not set" # notice we don't check for existence of arg 3, to allow the query semantics # of git-symbolic-ref to also work [ -z "$2" ] && die "usage: symbolic-ref /path/to/repo.git " # all the can_* functions set $repo can_write $1 || die "no write permissions on $repo" to=$repo shift # change head cd $GL_REPO_BASE_ABS/$to.git git symbolic-ref "$@" sitaramc-gitolite-6857652/contrib/adc/trash000077500000000000000000000024541171610012500206000ustar00rootroot00000000000000#!/bin/sh . $(dirname $0)/adc.common-functions # options settable in adc.common-functions are # TRASH_CAN -- where the trashed repos are moved. Can be anywhere that the # hosting user has write access to. Does not have to be (and ideally # should NOT be) inside $GL_REPO_BASE_ABS (but see note below) # TRASH_SUFFIX -- a timestamp, (ideally and by default), to be # suffixed to the moved repo # helper ADCs: list-trash, restore # NOTE: although I would NOT advise it in the interests of keeping things # simple, it *is* possible to have even deleted repos be *directly* accessible # via normal gitolite mechanisms (clone, etc). Here's how: # (1) make TRASH_CAN point somewhere *within* $REPO_BASE # (2) change TRASH_SUFFIX to something that has a .git at the end, like: # TRASH_SUFFIX=`date +%Y-%m-%d-%H-%M-%S`.git # (3) set ACL rules in conf/gitolite.conf for repos named # deleted/foo/sitaram/bar.* # cd to repo base and make sure arg1 is a valid repo (also sets $repo) valid_owned_repo $1 mkdir -p $TRASH_CAN/$repo 2>/dev/null || die "failed creating directory in trashcan" [ -d $TRASH_CAN/$repo/$TRASH_SUFFIX ] && die try again in a few seconds mv $repo.git $TRASH_CAN/$repo/$TRASH_SUFFIX echo "$repo moved to trashcan. Please run the 'help' adc for more info." sitaramc-gitolite-6857652/contrib/adc/unlock000077500000000000000000000004661171610012500207530ustar00rootroot00000000000000#!/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 touch $repo.git/gl-rm-ok echo "$repo has been unlocked. Please run the 'help' command for more info." sitaramc-gitolite-6857652/contrib/adc/watch000077500000000000000000000067451171610012500205740ustar00rootroot00000000000000#!/bin/sh . $(dirname $0)/adc.common-functions # Copyright 2010 Massachusetts Institute of Technology # # Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php # This "watch" ADC can be used to maintain a list of watchers to a # repository. You can then perform actions on the watchlist from a # hook or ADC. # # An obvious function of this would be to maintain a list of email # addresses that are sent commit notifications. There are however # unlimited uses, such as: # # * Specifying Twitter accounts that should receive notifications # of new clones created via the "fork" ADC # * Specifying hostnames of machines that should be poked to update # their repository web views # * Etc... # # The parameters are the repository path, "add"/"remove", and the # identifier. These are added along with your system identifier to a # gl-watchers file. However, there is an optional argument that allows you # specify what the identifier is used for. This allows you to perform # a default action if there is no optional argument, or a specific action # depending on what the optional argument. For instance, the gl-watchers # file on the server might look like this (note the actions described would # only occur if you actually code it in!): # # mitchell mitchell@kde.org [no optional argument; would default to email] # mitchell mitchell@kde.org email [will send email on pushes] # mitchell projects.kde.org httppost [will send commit details in an # HTTP POST to proejcts.kde.org] # # # Note that there is no authentication here; any user is allowed to # set any identifier, although users can only modify their own # identifiers. (This applies of course only to repositories a user can read # in the first place!) This script can easily be modified to only allow only # administrators (those with write access to the gitolite # administration repository) to add or remove watchers. See the # commented code below; uncomment it to enable this functionality. # Adminstrators can use the "sudo" ADC to add entries for specific # users. # Uncomment this and comment the corresponding block below to # restrict this functionality to administrators only #get_rights_and_owner gitolite-admin #[ -z "$perm_write" ] && die "just *what* are you trying to pull, young man?" #get_rights_and_owner $1; # all the can_* functions set $repo can_read $1 || die "no read permissions on $repo" cmd=$2 identifier=$3 arg=$4 [ -z "$cmd" -o -z "$identifier" -o "$cmd" != "add" -a "$cmd" != "remove" ] && die "paramters must be " if [ ! -z "$arg" ] then identarg="$identifier $arg" else identarg="$identifier" fi cd $GL_REPO_BASE_ABS/$repo.git [ ! -e gl-watchers ] && { touch gl-watchers || die "cannot create blank watchers file"; } [ ! -r gl-watchers ] && die "cannot read watchers file" grep "^$GL_USER $identarg$" gl-watchers > /dev/null found=$? [ $found -eq 0 -a $cmd == "add" ] && die "There is already a watch \"$identarg\" for user $GL_USER" [ $found -ne 0 -a $cmd == "remove" ] && die "No watch \"$identarg\" found for user $GL_USER" [ $cmd == "add" ] && echo "$GL_USER $identarg" >> gl-watchers && { echo "Added a watch \"$identarg\" for user $GL_USER"; exit 0; } [ $cmd == "remove" ] && sed -i -e "/^$GL_USER $identarg$/d" gl-watchers && { echo "Removed a watch \"$identarg\" for user $GL_USER"; exit 0; } die "16 cores, 320GB of RAM, 4TB of disk, and you give me a command I am not programmed to do. Humans..." sitaramc-gitolite-6857652/contrib/adc/who-pushed000077500000000000000000000017771171610012500215510ustar00rootroot00000000000000#!/bin/bash # find the last person to push the given commit # XXX we assume the logfile names have been left as default, or at least, if # changed, in such a way that when sorted by "ls", they come up oldest first . $(dirname $0)/adc.common-functions sha=$2 [ -n "$sha" ] || die Usage: ssh ... who-pushed reponame SHA \# at least first few hex digits # all the can_* functions set $repo can_read $1 || die "no read permissions on $repo" cd $GL_REPO_BASE_ABS/$repo.git logdir=$(dirname $GL_LOG) # uncodumented env var ;-) ls $logdir | tac | while read lf do < $logdir/$lf perl -ne "print if /receive-pack.*\s$repo\s/" | cut -f1,2,3,5- | tac done | while read ts who IP perm old new repo ref rule do save_old=$old [ "$new" = "00000000000000" ] && continue [ -z "$old" ] && continue [ "$old" = "00000000000000" ] && old= [ -n "$old" ] && old=$old.. git rev-list $old$new 2>/dev/null | grep ^$sha >/dev/null && printf "$ts $who $IP $perm $save_old $new $repo $ref $rule\n" done sitaramc-gitolite-6857652/contrib/emacs/000077500000000000000000000000001171610012500200655ustar00rootroot00000000000000sitaramc-gitolite-6857652/contrib/emacs/README-emacs.mkd000066400000000000000000000003341171610012500226050ustar00rootroot00000000000000# F=emacsmode_ Emacs major mode for gitolite.conf [Emacs][] major mode for `gitolite.conf` can be found here: - [GitHub][] [Emacs]: http://www.gnu.org/software/emacs [GitHub]: http://github.com/llloret/gitolite-emacs sitaramc-gitolite-6857652/contrib/gerrit.mkd000066400000000000000000000132501171610012500207670ustar00rootroot00000000000000# F=gerrit comparing gerrit and gitolite Gerrit and gitolite have too many high level differences. Size is most visible of course: 56000 lines of Java versus 1300 lines of perl+shell, according to David A. Wheeler's 'SLOCCount' tool. Gerrit needs a database (it comes with a perfectly usable one, or I believe you can use any of the usual suspects), and even comes with its own ssh server and git server, and since the git engine is internal it probably has to include a lot of things that normal git already has; I wouldn't know for sure. Gerrit allows a lot more de-centralisation in managing the system, and of course excels at code review, and it seems geared to really large, open source-ish projects with lots of contributors. Gitolite works on a pure command-line install and a plain text file config, and is designed to run unobtrusively and quite transparently to all developers -- other than sending the admin their pubkey, nothing really changes for them in their workflow, toolset, etc. The "lite" in the name still holds, despite all the extra features being pumped in! Gitolite was mainly written for a corporate environment, where we really, really, need branch-level ACLs. While they would certainly love the code review part, things like *voting* on a change, and so on seem a bit alien, and it seems to me that code review itself is more likely to be a hierarchical thing, not a peer-to-peer, "anyone can comment" thing. I could be wrong. ---- In short, gitolite doesn't do the main thing that gerrit does, and gerrit is so much bigger than gitolite in so many ways, it seems really odd to compare them at all. However, it seems gerrit comes closest to gitolite in terms of flexibility of access control, which is gitolite's main strength, so I thought it would be useful to compare gitolite with just what is in the "access-control.html" in the gerrit war file. Or see [this][gdac]. [...and stop sniggering at the "svn" in the link dammit!] Note that I don't necessarily list something that both tools have. (For example, per-user branches denoted by `/USER/` in gitolite and `$(username)` in gerrit, or being able to distinguish between creating a branch and pushing to an already created one, etc). [gdac]: http://gerrit.googlecode.com/svn/documentation/2.1.2/access-control.html [jwzq]: http://regex.info/blog/2006-09-15/247 **Administrators**: anyone who has gitolite-admin push privs **Anonymous Users**: gitolite doesn't do that, though the "ssh-plus" branch, combined with git-daemon2 (Ilari) will allow that in future. When git-daemon2 becomes mainstream, the supporting code in this branch will also be merged into "master". **Registered Users**: @all **Account Groups**: @groups in gitolite. We do allow them to be nested, although the parsing is single-pass. We also don't have group `foo-admin` managing membership to group `foo` though; all groups are managed by the eqvt of "Administrators". **Project ACLs**: first, let's remember (again) that we don't have any of the code review stuff :) * This is a subjective point, but gerrit doesn't give permissions to individual users, only groups. People who have several small projects (only one QA, one integrator, etc.), would have to create lots of 1-man groups, which could be cumbersome. * **Evaluation** order and priority of access control rules are different. Gerrit goes by specificity, gitolite goes by sequence. It shouldn't matter; they're probably equivalent except perhaps in some far-fetched scenarios. * Update 2010-10-24: as per [this][gitlog1] Gerrit now has *read* access control at the branch level -- they can afford to do that because they have a full jgit stack to play with. Even then it was not easy -- they had to implement a callback from jgit to gerrit for the fetch, *and* deal with evil clients that might try to read an object by *pushing* a supposed change on top of a SHA that they know but don't actually have. (You'll have to think about this carefully; it may not be immediately obvious to people who do not know the ref-exchange in the git protocol). Gitolite is dependent on git itself to provide that -- it just cannot be done without support from git core. I can see some corporates drooling at this possibility (makes no sense for open source projects IMO) ;-) My normal recommendation is to **use separate repos** if you really need this while continuing to use gitolite. Much simpler and easier to audit and to convince auditors that "those people can't see that code". **Categories**: * gitolite doesnt have an "owner" for each project in any administrative sense. Perhaps you could consider whoever has `RW+` perms to be an owner but it doesn't go beyond what that implies. * gitolite doesnt do anything special to signed or annotated tags * Force push is the same as delete by default. However, gitolite can now allow these two separately if that's how you need it. Of course, direct pushing clashes with code review, and gerrit recommends that if you want code review you should not use this feature. [Normal pushes in gerrit go through a temp branch that is moved to the correct one after a review is done; direct pushes are all that gitolite has]. * author/committer identity: checking these fields in pushed commits is likely to be important in some projects, but gitolite doesn't have any notion of this. Hmm... I smell another feature in the future :) The rest of it is in areas that the two tools have no overlap on (again, code review being the main thing), or, as I said at the top, features that both tools have. [gitlog1]: http://colabti.org/irclogger/irclogger_log/git?date=2010-09-17#l2710 sitaramc-gitolite-6857652/contrib/gitolite-tools.mkd000066400000000000000000000011211171610012500224430ustar00rootroot00000000000000# F=gitolite_tools_ gitolite-tools gitolite-tools is a collection of external git commands to work with gitolite server and repositories: * git gl-info - Display gitolite server information * git gl-ls - List accessible gitolite repositories * git gl-desc - Display or edit description of gitolite wildcard repositories * git gl-perms - Display or edit permissions of gitolite wildcard repositories * git gl-htpasswd - Set password for gitweb/apache ## Homepage The project in GitHub: [http://github.com/tmatilai/gitolite-tools](http://github.com/tmatilai/gitolite-tools) sitaramc-gitolite-6857652/contrib/gitweb/000077500000000000000000000000001171610012500202565ustar00rootroot00000000000000sitaramc-gitolite-6857652/contrib/gitweb/gitweb.conf000066400000000000000000000037141171610012500224130ustar00rootroot00000000000000# -------------------------------------------- # Per-repo authorization based on gitolite ACL # Include this in gitweb.conf # See doc/3-faq-tips-etc.mkd for more info # please note that the author does not have personal experience with gitweb # and does not use it. Some testing may be required. Patches welcome but # please make sure they are tested against a "github" version of gitolite # and not an RPM or a DEB, for obvious reasons. # HOME of the gitolite user my $gl_home = $ENV{HOME} = "/home/git"; # 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 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"; # now get gitolite stuff in... unshift @INC, $ENV{GL_BINDIR}; require gitolite_rc; gitolite_rc -> import; require gitolite; gitolite -> import; # 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}; $export_auth_hook = sub { my $repo = shift; # gitweb passes us the full repo path; so we strip the beginning # and the end, to get the repo name as it is specified in gitolite conf return unless $repo =~ s/^\Q$projectroot\E\/?(.+)\.git$/$1/; # check for (at least) "R" permission my ($perm, $creator) = &repo_rights($repo); return ($perm =~ /R/); }; sitaramc-gitolite-6857652/contrib/ldap/000077500000000000000000000000001171610012500177155ustar00rootroot00000000000000sitaramc-gitolite-6857652/contrib/ldap/README-ldap.mkd000066400000000000000000000006721171610012500222720ustar00rootroot00000000000000# F=ldap_helpers ldap helper programs These programs were contributed by the Nokia MeeGo folks. The first 2 are perl and shell verisions of programs meant to be used as `$GL_GET_MEMBERSHIPS_PGM` (see [this][ldap] for more). * ldap-query-example.pl * ldap-query-example.sh The third program is meant to be installed as an adc (admin-defined command, see [here][ADCs]), and helps users change their LDAP passwords. * passwd Enjoy! sitaramc-gitolite-6857652/contrib/ldap/ldap-query-example.pl000066400000000000000000000053431171610012500237730ustar00rootroot00000000000000#!/usr/bin/perl # # Copyright (c) 2010 Nokia Corporation # # This code is licensed to you under MIT-style license. License text for that # MIT-style license is as follows: # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. # # ldap-query.pl # # this script is used to perform ldap querys by giving one argument: # - the user UID for ldap search query # # NOTICE: This script requires libnet-ldap-perl package to be installed # to the system. # use Net::LDAP; # Script requires user UID as the only parameter if ( $ARGV[0] eq '' || $ARGV[1] ne '' ) { print "ldap-query.pl requires one argument, user's uid\n"; exit 1; } $user = $ARGV[0]; # Create communication structure for LDAP connection $ldap = Net::LDAP->new( 'localhost', port => 389, debug => 0, timeout => 120, version => 3 ) or die "$@"; # Bind to LDAP with proper user $ldapret = $ldap->bind( 'cn=administrator,o=company', password => '5ecretpa55w0rd' ); die "$ldapret->code" if $ldapret->code; # Create filter for LDAP query my $filter = '(&'. '(objectClass=groupAttributeObjectClassName)'. "(uid=$user)". ')'; # Execute the actual LDAP search to get groups for the given UID $ldapret = $ldap->search( base => 'ou=users,ou=department,o=company', scope => 'subtree', filter => $filter ); # Parse search result to get actual group names my $default_group = ''; my $extra_groups = ''; foreach my $entry ( $ldapret->entries ) { $default_group = $entry->get_value( 'defaultGroupAttributeName' ) . ' ' . "$default_group"; $extra_groups = $entry->get_value( 'extraGroupsAttributeName' ) . ' ' . "$extra_groups"; } # Return group names for given user UID print "$default_group" . "$extra_groups"; sitaramc-gitolite-6857652/contrib/ldap/ldap-query-example.sh000066400000000000000000000051601171610012500237670ustar00rootroot00000000000000#!/bin/sh # # Copyright (c) 2010 Nokia Corporation # # This code is licensed to you under MIT-style license. License text for that # MIT-style license is as follows: # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. # # ldap-query.sh # # this script is used to perform ldap querys by giving one argument: # - the user UID for ldap search query # # NOTICE: This script requires ldap-utils and sed to be installed to the system. # # Script requires user UID as the only parameter # if [ $# -ne 1 ] then echo "ldap-query.sh requires one argument, user's uid" exit 1 fi uid_param="${1}" # Set needed LDAP search tool options for the query ldap_host="localhost" ldap_binddn="cn=administrator,o=company" ldap_bindpw="5ecretpa55w0rd" ldap_searchbase="ou=users,ou=department,o=company" ldap_scope="subtree" # Construct the command line base with needed options for the LDAP query ldap_options="-h ${ldap_host} -x -D ${ldap_binddn} -w ${ldap_bindpw} -b ${ldap_searchbase} -s ${ldap_scope}" # Construct the search filter for the LDAP query for the given UID ldap_filter="(&(objectClass=groupAttributeObjectClassName)(uid=${uid_param}))" # Construct return attribute list for LDAP query result attr1="defaultGroupAttributeName" attr2="extraGroupsAttributeName" ldap_attr="${attr1} ${attr2}" # Execute the actual LDAP search to get groups for the given UID ldap_result=$(ldapsearch ${ldap_options} -LLL ${ldap_filter} ${ldap_attr}) # Edit search result to get space separated list of group names ldap_result=$(echo ${ldap_result} | sed -e "s/.* ${attr1}://" -e "s/ ${attr2}://") # Return group names for given user UID echo ${ldap_result} sitaramc-gitolite-6857652/contrib/ldap/passwd000066400000000000000000000076321171610012500211510ustar00rootroot00000000000000#!/usr/bin/perl use Net::LDAP; use Term::ReadPassword; use Digest::SHA1; use MIME::Base64; use Data::UUID; use Crypt::Cracklib; my $PASSWD_MIN_LEN = 8; my $password; # parse RC file # $ENV{GL_RC} = "/home/gitolite/.gitolite.rc"; die "parse $ENV{GL_RC} failed: " . ($! or $@) unless do $ENV{GL_RC}; # These come from .gitolite.rc file our ($GL_LDAP_HOST, $GL_LDAP_BIND_DN, $GL_LDAP_BIND_PASSWORD, $GL_LDAP_USER_DN); $Term::ReadPassword::ALLOW_STDIN = 1; # NOTICE: For some reason Perl fails to disable terminal echo # so following warning about ECHO must be given to the user # Warn about password echo because of bugs in Perl ReadPasword print "\nNOTE THAT THE PASSWORD WILL BE ECHOED TO THE SCREEN!\n" . "Please make sure no one is shoulder-surfing, and make sure\n" . "you clear your screen and scrollback history after you are done\n" . "(or close your terminal session).\n\n"; print "Please type in your new password at the prompt.\n\n" . "Following special keys are available while typing:\n" . " key to remove the last character\n" . " to remove all characters\n" . " to terminate password change operation\n" . " to end password typing\n"; while ( 1 ) { print "\n"; # Start reading with new line $password = read_password("Enter new password: ", 0, 1); # Check the validity of new password if ( length( $password ) >= $PASSWD_MIN_LEN # require minimum length && $password =~ /([\x20-\x7E])/ # require printable characters && $password =~ /[a-z]/ # require lower case letter && $password =~ /[A-Z]/ # require upper case letter && $password =~ /[0-9]/ # require number && check( $password ) ) # require other than dictionary words { # Re-enter new password to check possible typos if ( $password ne read_password("Enter password again: ") ) { print "Passwords do not match!\n"; redo; } else { last; # Password is valid and there are no typos, so break out } } else { # Given password is not valid print "Password must contain at least $PASSWD_MIN_LEN characters and numbers,\n" . "must have both upper and lower case characters,\n" . "can have special characters like !,",#,...\n" . "but cannot be any valid dictionary word.\n"; redo; } } # Create hash from the password to be stored to the LDAP my $ctx = Digest::SHA1->new(); my $ug = new Data::UUID; my $salt = $ug->create_b64(); $ctx->add( $password ); $ctx->add( $salt ); $password = '{SSHA}' . encode_base64( $ctx->digest . $salt, '' ); # Create communication structure for LDAP connection my $ldap = Net::LDAP->new( $GL_LDAP_HOST ) or die "$@"; my $r = $ldap->start_tls( verify => 'none', sslversion => 'tlsv1' ); if ( $r->code ) { print "Password handling failed with $r->code return code!\n"; log_it( "Password change, LDAP connection failed for $ENV{GL_USER}" ); exit 1; } # Bind to LDAP with proper user $r = $ldap->bind( $GL_LDAP_BIND_DN, password => $GL_LDAP_BIND_PASSWORD ); if ( $r->code ) { print "Password update failed with $r->code return code!\n"; log_it( "Password change, LDAP bind failed for $ENV{GL_USER}" ); exit 1; } # Update new password to the LDAP $r = $ldap->modify( "uid=$ENV{GL_USER}, $GL_LDAP_USER_DN", replace => { 'userPassword', $password } ); if ( $r->code ) { print "Password change failed!\n" . "Please contact administrator to change password.\n"; # log_it( "Password change, LDAP modify failed for $ENV{GL_USER}" ); } else { print "Password changed succesfully.\n"; # log_it( "Password change, LDAP modify done for $ENV{GL_USER}" ); } $r = $ldap->unbind(); sitaramc-gitolite-6857652/contrib/mirrorconf-helper.sh000077500000000000000000000131711171610012500227740ustar00rootroot00000000000000#!/bin/bash # tool to make adding/editing products easier # see subconf-example.mkd or html version somewhere # PRE-REQUISITES # 1. gitolite installed on all servers # 2. the gitolite-admin repo is also mirrored # run this program ONLY in a clone of a gitolite-admin repo in a committed # state. This way a "git diff" will tell you what changed, and a "git status" # will tell you what new files were created, and you can rollback if needed. usage() { cd $od echo commands: grep '^#.*$0' $0 | cut -c7- echo echo '(please read the inline documentation for more info)' } # ------------------------------------------------------------------------------ # COMMANDS # ------------------------------------------------------------------------------ # adding a new host: # $0 newhost hostname admin-username # NOTE: this requires you to first add the newhost to the gitolite.conf file # in the list of slaves for the admin repo. That is manually done; this # script will not do it. You will also have to ensure that the new server # being added has been updated and is receiving changes to the admin repo # automatically. # DO NOT PROCEED OTHERWISE. If necessary, check by making a dummy change to # the admin repo and pushing, then make sure the new server has received the # change. # ------------------------------------------------------------------------------ # adding a new product to a master host: # $0 newprod hostname product-name # NOTE: the host admin must first create and propagate the # master/host/prod.conf file (see section 3, "host admins only"). # ------------------------------------------------------------------------------ # adding a new slave to a master/prod combo # $0 newslave master-hostname product-name slave-hostname # ------------------------------------------------------------------------------ # new *server*: edit gitolite.conf manually (slaves list for the admin repo) # everything else is done by this tool # ASSUMPTIONS: we are in a gitolite-admin clone somewhere die() { echo "$@" >&2; usage; exit 1; } finish() { echo >&2; exit 0; } # go to the conf directory od=$PWD; export od git rev-parse --show-toplevel >/dev/null || die not in a git directory? cd $(git rev-parse --show-toplevel) cd conf || die cant find a conf/ subdirectory [ -f gitolite.conf ] || die cant find a gitolite.conf file verify_host() { grep config.*gitolite.mirror gitolite.conf | perl -pe 's/"/ " /g' | grep " $2 " >/dev/null || die "$2 not found in gitolite.conf mirror config" } update_file() { echo >&2 echo >&2 ==== appending lines to $1 ==== tee -a $1 } # ------------------------------------------------------------------------------ # adding a new host: # newhost hostname admin-username [ "$1" == "newhost" ] && { [ -z "$2" ] && die "need hostname" verify_host master $2 [ -f master/$2.conf ] && die "master/$2.conf already exists" [ -z "$3" ] && die "need admin username for host $2" ( echo printf "@$2\t= $2/..*\n" | expand -32 ) | update_file host-product-map.conf # setup the first line of the NAME-restrictions.conf file [ -f NAME-restrictions.conf ] || echo "repo gitolite-admin" > NAME-restrictions.conf ( echo printf "RW\t= $3\n" | expand -40 printf "RW NAME/conf/master/$2/\t= $3\n" | expand -40 ) | update_file NAME-restrictions.conf mkdir -p master ( echo echo "include \"master/$2/*.conf\"" ) | update_file master/$2.conf finish } # ------------------------------------------------------------------------------ # adding a new product to a master host: # newprod hostname product-name [ "$1" == "newprod" ] && { [ -z "$2" ] && die "need hostname" verify_host master $2 [ -f master/$2.conf ] || die "host $2 not found; forgot to run 'newhost'?" [ -z "$3" ] && die "need product name to add" [ -f master/$2/$3.conf ] || die "master/$2/$3.conf not found; contact host-admin for $2" [ -f mirrors/$2/$3.conf ] && die "mirrors/$2/$3.conf already exists" ( echo printf "@$2\t= $3/..*\n" | expand -32 ) | update_file host-product-map.conf finish } # ------------------------------------------------------------------------------ # adding a new slave to a master/prod combo # newslave master-hostname product-name slave-hostname [ "$1" == "newslave" ] && { [ -z "$2" ] && die "need hostname" verify_host master $2 [ -f master/$2.conf ] || die "host $2 not found; forgot to run 'newhost'?" [ -z "$3" ] && die "need product name to add" [ -f master/$2/$3.conf ] || die "master/$2/$3.conf not found; contact host-admin for $2" [ -z "$4" ] && die "need slave name to add" verify_host slave $4 # first create lines in slave/slavename/mastername.conf f="slave/$4/$2.conf" i="$2/$3.conf" [ -f $f ] && grep "$i" "$f" >/dev/null && die "$f already contains lines for $i" mkdir -p slave/$4 ( echo echo "include \"master/$i\"" echo "include \"mirrors/$i\"" ) | update_file "$f" # now check how many slaves we have for this and overwrite mirrors/$2/$3.conf sl=$(echo slave/*/$2.conf | perl -pe "chomp; s(slave/(.*?)/$2.conf)(\$1)g") f="mirrors/$2/$3.conf" [ -f $f ] && { echo >&2 echo >&2 "==== overwriting file $f; old contents:" cat >&2 $f > $f } mkdir -p mirrors/$2 ( echo "repo $3/..*" echo " config gitolite.mirror.master = \"$2\"" echo " config gitolite.mirror.slaves = \"$sl\"" ) | update_file $f finish } usage sitaramc-gitolite-6857652/contrib/mirroring-complex-example.mkd000066400000000000000000000257711171610012500246140ustar00rootroot00000000000000# F=mirr_complex_example semi-autonomous mirroring setup example This document describes one way to do this. Gitolite is powerful so you can probably find other ways to suit you. ## overview of problem The example is from real life, with the following characteristics: * multiple servers (hosts) * multiple "products", each product has one or more git repos * different products are "native to" (mastered on) different hosts * a product may be mirrored to zero or more other hosts (mirrored to zero hosts means it is **local** to the host) The admin requirements are: * the overall system will have one or more **master admins**; they manage the main config file, for instance. * each host will have one or more **host admins** * these host admins should be allowed to create any repos they need (within one of a set of directory names allocated to them), and assign access to whomever they please * they should not be able to "step on each other" -- setup access rules for repos not in their control The following can only be done by the master admins: * authentication (ssh keys) are centrally managed and distributed. Host admins should not be allowed to do this. * mirroring setup -- who's the master and who're the slaves for any repo * allowing redirected pushes from slaves ## overview of setup We will use p1 as the product, with sam as the master and frodo as a slave. Assume equivalent text/code for other product/master/slave combos. This setup imposes the condition that all repos should be under some directory name; either a product name or, for local repos, a hostname. In our example, these directory names would be p1 or sam on the host sam, and frodo on the host frodo. ### gitolite feature recap We use [delegation][deleg], to ensure that admins for sam can only write files whose names start with `master/sam/`. The actual files they will write are `master/sam/p1.conf` etc., one for each product that is mastered on their server. We use [subconf][]. When you say `subconf "path/to/foo.conf`, then within that file (and anything included from it), access can only be defined for repos that regex-match one of the elements of `@foo`. ## pre-requisites First, install mirroring on all servers according to the main mirroring document. Set it up so that the gitolite-admin repo is mastered at one server and everyone else slaves it. Also, after (or during) the normal mirroring install, edit `~/.gitolite.rc` on all servers and set `$GL_WILDREPOS` to 1 (from its default of 0). ## quick setup * edit your `gitolite.conf` file as given in step 1 below * ignore all the comments, even the ones that tell you to do something :-) * change only the names of the admin users, and the names of the servers in the config lines. Everything else stays the same * now copy `mirror-conf-helper` from the contrib directory to your home or some easy to type place, and run it to setup your hosts, products, and slaves A typical sequence with that script is: # cd to your gitolite-admin clone # create a new host ~/mirror-conf-helper newhost sam sam-admin # create the actual repository and access rules. This is done by the host # admin for the host on which it is mastered (or you can do it yourself). # See step 3 below for details. mkdir -p conf/master/sam vim conf/master/sam/p1.conf # now add in some "repo p1/..." and "RW ..." lines for the repos in # product p1 and save # add product p1 to the list of repos sam is allowed to control ~/mirror-conf-helper newprod sam p1 # add a slave ~/mirror-conf-helper newslave sam p1 frodo You can then treat the detailed steps described below as extra information or "background reading" ;-) ## F=mirrexsteps_ step by step If the script is not cutting it for you and want to vary the technique for some reason, or you simply want to gain a better understanding of what is happening, it may be better to do each step manually instead of just using the script. **Note**: all files mentioned below are assumed to be under **`conf/`**. The only place where you have to explicitly state this is in the delegation code in the appendix. The rest of the time, "conf/" is assumed. ### (1) `gitolite.conf` The main config file has these items in it. **Please add them in this order**. If you follow this document completely, your gitolite.conf file can be pretty static, changing only if the master admin changes or you need to add a new host as slave to the gitolite-admin repo. Therefore you can set it up first. Here's what it looks like: # (1.1)--------------------------------------------------------------------- # First the main setup: @master-admins = sitaram dilbert repo gitolite-admin RW+ = @master-admins config gitolite.mirror.master = "master" config gitolite.mirror.slaves = "list of slave servers" # you cannot use continuation lines for this; sorry! You have to list # them all in ONE long line within one set of double quotes... # (1.2)--------------------------------------------------------------------- # If you have any files with "convenience" group definitions, pull them in: include "groups/users.conf" include "groups/repos.conf" # (1.3)--------------------------------------------------------------------- # Next is delegation. If you don't want delegation, omit this section, # and replace all "subconf" commands with "include" in the rest of this # document. include "host-product-map.conf" # create this file; see step A1 in appendix A repo gitolite-admin # now that you're adding a NAME/ section, you need this for master # admins to retain their access RW+ NAME/ = @master-admins include "NAME-restrictions.conf" # create this file; see step A2 in appendix A # (1.4)--------------------------------------------------------------------- # Now you include the access rules for native repos # (example: master/sam.conf) subconf "master/HOSTNAME.conf" # (1.5)--------------------------------------------------------------------- # After this you have the mirror config for native repos. We place this # *after* the access rules above, to make sure we override any mirror # config lines accidentally added by a host admin! # (example: mirrors/sam/p1.conf) include "mirrors/HOSTNAME/*.conf" # (1.6)--------------------------------------------------------------------- # Now we pull in all setup (mirror config *and* access rules) for # non-native repos. For the product "p1", this file will get pulled in # when the config is processed on frodo. # (example: slave/frodo/sam.conf) subconf "slave/HOSTNAME/*.conf" You'll get some warnings about missing include files; ignore them. ### (2) `master/sam.conf` For each host sam, one file called `master/sam.conf` is needed. This file contains just one line: include "master/sam/*.conf" It is pulled in by the main config file using `subconf "master/HOSTNAME.conf"`, which -- on host sam -- translates to `subconf "master/sam.conf"`. The only purpose of this is to setup the subconf restriction on the combined contents of `master/sam/*.conf`. ### (3) host admins only -- `master/sam/p1.conf` (recap: the host admins for sam can only write files in `master/sam`). For each product p1 with master on host sam, the admins for host sam will create a file `master/sam/p1.conf`. This file will contain reponames (must start with `p1/`) and access rules for these repos. If they have some common groupnames etc., they can probably put them in `master/sam/users.conf` or some such file and pull those in into each of their product.conf files. By default, everything is local to their server. (Mirroring can only be setup by the master admins). ### (4) `mirrors/sam/p1.conf` For each product p1 mastered on host sam, a file called `mirrors/sam/p1.conf` will be created, containing mirror config lines for all repos of product p1. In this case, it could be repo p1/..* config gitolite.mirror.master = "sam" config gitolite.mirror.slaves = "frodo" If this file does not exist, p1 is local to sam and not mirrored. ### (5) `slave/frodo/sam.conf` For each product that slave frodo gets from master sam, this file has the following lines # pull in the access lines include "master/sam/p1.conf" # pull in the mirror config lines include "mirrors/sam/p1.conf" This file is pulled in on a slave server via a `subconf slave/HOSTNAME/*.conf` line in the main config file. On frodo, this would pull in `slave/frodo/sam.conf` (among others), establishing, again, a subconf restriction on `@sam`. Security note: what this achieves is that the access lines, which were written by sam's admins, are parsed on frodo *under the subconf restriction of "sam"*. This is important to prevent sam's admins from writing rules for repos they don't own and having them processed on other servers! ### (6) manual sync The new repo(s) you just created would not have been synced up to frodo. You can either make an empty commit and push, or log on to sam and run gl-mirror-shell request-push p1/reponame ## next steps Once you've done the initial setup, here's what ongoing additions will require. * any new repos that are created for the same *product* require only step 3 * a new *product* will require steps A1, 3, 4 and 5 * a new *host* will require additions in all the steps, including adding the hostname in the slaves list for the admin repo (this is in the main gitolite.conf file) ## F=mirrappA_ appendix A: delegation helper files These two files were briefly mentioned in the delegation setup. (A1) `conf/host-product-map.conf` has the following contents: # For each host foo, there will be one line: # @foo = foo/..* # line (for local repos for the host). # For each product bar whose master is a host foo, there will be one line: # @foo = bar/..* # to add bar/..* to the allowed patterns when subconf "foo" is in effect # ------------------------------------------------------------------------------ # so for our example: @sam = sam/..* @sam = p1/..* @frodo = frodo/..* (A2) `conf/NAME-restrictions.conf` has the following contents: # For each host foo, there will be two lines: # RW = username-of-foo-host-admin # RW NAME/conf/master/foo/ = username-of-foo-host-admin # IMPORTANT: DO NOT MISS THE TRAILING SLASH IN THE LINE ABOVE! # ------------------------------------------------------------------------------ repo gitolite-admin # this line is required RW = sam-admin RW NAME/conf/master/sam/ = sam-admin RW = frodo-admin RW NAME/conf/master/frodo/ = frodo-admin sitaramc-gitolite-6857652/contrib/monkeysphere.mkd000066400000000000000000000041641171610012500222100ustar00rootroot00000000000000# F=monkeysphere (contributed doc: integrating gitolite with monkeysphere) This document attempts to describe one way to integrate [Monkeysphere](http://web.monkeysphere.info/) authentication with [gitolite](http://github.com/sitaramc/gitolite). We presuppose that you have a system with a new enough version of Monkeysphere to support ssh `authorized_keys` options, and that you are not making use of monkeysphere-authentication on this system. As a first step, import the key or keys you wish to act as Monkeysphere certifiers into the GnuPG public keyring of the gitolite user (for example, `gpg --keyserver pool.sks-keyservers.net --recv-keys B0AE9A02`) Then edit such keys (`gpg --edit B0AE9A02`) and assign them *ultimate* ownertrust. Next install a script of this nature as `post-update.secondary` in the `hooks/` directory of the `gitolite-admin` repository. You can also follow the "using hooks" section in gitolite's "admin" document to let gitolite put your new hook in the correct place. #!/bin/zsh # this should use locking pushd ${GL_ADMINDIR} if [[ -d monkeydir ]] then cp ~/.monkeysphere/authorized_user_ids ~/.monkeysphere/old-authorized_user_ids rm -f ~/.monkeysphere/new-authorized_user_ids for i in monkeydir/*.pub do username=$i:t:r for j in ${(f)"$(<$i)"} do cat >> ~/.monkeysphere/new-authorized_user_ids < the partial copy # "main", "pco" => the one which we are a "partial copy of" my $main=`git config --file $ENV{GL_REPO_BASE_ABS}/$ENV{GL_REPO}.git/config --get gitolite.partialCopyOf`; chomp ($main); exit 0 unless $main; 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; # go to the main repo. Find a list of all the refs it has, and for each one, # check if this user is allowed to read that ref from our repo. If he is, add # it to a list. my %allowed; wrap_chdir("$ENV{GL_REPO_BASE_ABS}/$main.git"); for my $ref (`git for-each-ref refs/heads '--format=%(refname)'`) { chomp($ref); my $ret = check_access($ENV{GL_REPO}, $ref, 'R', 1); $allowed{$ref} = 1 unless $ret =~ /DENIED/; } # now go to our repo and... wrap_chdir("$ENV{GL_REPO_BASE_ABS}/$ENV{GL_REPO}.git"); # delete all existing refs that are not "allowed" (e.g., refs that were # previously allowed but now are not, due to config file/rules change) for my $ref (`git for-each-ref refs '--format=%(refname)'`) { chomp($ref); next if $allowed{$ref}; system("git", "update-ref", "-d", $ref); } # now copy all allowed branches (and their tags, implicitly) for my $ref (sort keys %allowed) { system("git", "fetch", "-f", "$ENV{GL_REPO_BASE_ABS}/$main.git", "$ref:$ref"); } # now allow the git operation to proceed exit 0 sitaramc-gitolite-6857652/contrib/partial-copy/partial-copy.mkd000066400000000000000000000130731171610012500245060ustar00rootroot00000000000000# F=partialcopy maintaining a partial copy of a repo The regular documentation on basic access control mentions [here][rpr_] that it is easy to maintain two repositories if you need a (set of) branch(es) to be "secret", with one repo that has everything, and another that has everything but the secret branches. Here's how gitolite can help do that sanely, with minimal hassles for all concerned. This will ensure the right branches propagate correctly when people pull/push -- you don't have to do anything manually after setting it up unless the rules change. To start with, here's a **NON-WORKING** config that merely describes what we're **trying** to achieve: # THIS WILL NOT WORK! repo foo - secret-1$ = wally RW+ dev/USER/ = wally RW+ = dilbert alice ashok wally We want Wally the slacker to not be able to see the "secret-1" branch. The only way to do this is to have two repos -- one with and the other without the secret branch. These two repos cannot share git objects (to save disk space) using hardlinks etc. Doing so would cause a data leak if Wally decides to stop slacking and start hacking. See my conversation with Shawn [here][gitlog1] for more on this, but it basically involves Wally finding out the SHA of one of the secret branches, pushing a branch that he claims to have built on that SHA, then fetching that branch again. It requires a serious understanding of the git transport protocol, how objects are sent/received, how thin packs are created, etc., to implement it. Or to convince yourself that someone's implementation is correct. Meanwhile, the method described here, once you accept the disk space cost, is quite understandable to mere mortals like me :-) In the above example you had 2 sets of read access -- (1) all branches (2) all branches except secret-1. If you end up with one more set (say, "all branches except secret-2") then you need one more repo to handle it. If you can afford the storage, the following recipe can certainly make it *manageable*. [gitlog1]: http://colabti.org/irclogger/irclogger_log/git?date=2010-09-17#l2710 ## first, as usual, the caveats! * if you change the config to disallow something that used to be allowed, any tags pointing to objects that Wally's repo acquired before the change, will keep coming back! That is, branch B1 had a tag T1 within it. Later, B1 was disallowed for Wally. However, Wally's repo will still retain the tag T1! So, if you ever disallow a branch that used to be allowed, it's best to purge Wally's repo manually and let it get rebuilt on the next access. Just delete it from the disk, push the gitolite-admin config to force it to re-create, then access it as a legitimate user. * this recipe has not been, and will not be, tested with smart http. * it probably won't play well with wildcard repos either; not tested. * finally, mirroring support for such repos has not been tested too. ## the basic idea The basic idea is very simple. * one repo is the "main" one. It contains all the branches, and is the one that people with full access will use. * the other repo (or all the other repos, if you have more than one set, as described above) is a "partial copy", with only a subset of the branches in the main repo. * every time someone accesses the partial copy, the branches that that user is allowed to read are fetched from the main repo. **See note in example below**. * every time someone pushes to the partial copy, the branch being pushed is sent back to the main repo before the update succeeds. The main repo is always the canonical/current one. The others may or may not be uptodate. ## the config file Here's what we actually need to put in the config file. Note that the reponames can be whatever you want of course. repo foo RW+ = dilbert alice ashok repo foo-partialcopy-1 - secret-1$ = wally R = wally RW+ dev/USER/ = wally config gitolite.partialCopyOf = foo **Important notes**: * Wally must not have any access to "foo". Absolutely none at all. * Wally's rules for `foo-partialcopy-1` must be written such that restricted branches are denied. You could list only the branches he's allowed to read, or you could deny the ones he's not allowed and add a blanket "R" for the others later, as in this example. Note that this is the same [deny][] logic that is normally used for write operations, but applied to "read" in this case. All we're doing is using this logic to determine what branches from `foo` are allowed to propagate to the partial copy repo. *This is NOT being used by git to restrict reads; at the risk of repetition, git does NOT have that capability*. * All the other users with access to `foo-partialcopy-1` must be under the same restrictions as Wally. So let's say Ashok is not allowed to view a branch called "USCO". That needs to be defined in yet another partial copy repo, and `ashok` must be removed from the access list for `foo`. ## the hooks The code for both hooks is included in the source directory `contrib/partial-copy`. Note that this is all done *without* touching gitolite core at all -- we only use two hooks; both described in the [hooks][] section. A pictorial representation of all the stuff gitolite runs is [here][flow]; it may help you understand the role that these two hooks are playing in this scenario. sitaramc-gitolite-6857652/contrib/partial-copy/t.sh000077500000000000000000000137711171610012500222140ustar00rootroot00000000000000#!/bin/bash # test script for partial copy feature # WARNING 1: will wipe out your gitolite.conf file (you can recover by usual # git methods if you need of course). # WARNING 2: will wipe out (completely) the following directories: rm -rf ~/repositories/{foo,foo-pc}.git ~/td # REQUIRED 1: please make sure rc file allows config 'gitolite.partialCopyOf'. # REQUIRED 2: please make sure you copied the 2 hooks in contrib/partial-copy # and installed them into gitolite # REQUIRED 3: the 'tsh' command and associated Tsh.pm in PATH # ---- set -e mkdir ~/td tsh "plan 83"; # ---- cd ~/gitolite-admin cat << EOF1 > conf/gitolite.conf # testing partial-copy repo gitolite-admin RW+ = tester repo testing RW+ = @all EOF1 tsh "## setup base conf add conf; commit -m start; empty; ok; push -f; ok" cat << EOF2 >> conf/gitolite.conf repo foo RW+ = u1 u2 repo foo-pc - secret-1$ = u4 R = u4 # marker 01 RW next = u4 RW+ dev/USER/ = u4 RW refs/tags/USER/ = u4 config gitolite.partialCopyOf = foo EOF2 tsh " ## setup partial-repos conf add conf; commit -m partial-repos; empty; ok; # /master.*partial-repos/ push; ok; /Init.*empty.*foo\\.git/ /Init.*empty.*foo-pc\\.git/ /u3.*u5.*u6/; !/u1/; !/u2/; !/u4/ " cd ~/td; rm -rf foo foo-pc tsh " ## populate repo foo, by user u1 # create foo with a bunch of branches and tags clone u1:foo /appear.*cloned/ cd foo dc a1; dc a2 checkout -b dev/u1/foo; dc f1; dc f2 checkout master; dc m1; dc m2 checkout master; checkout -b next; dc n1; dc n2; tag nt1 checkout -b secret-1; dc s11; dc s12; tag s1t1 checkout next; checkout -b secret-2; dc s21; dc s22; tag s2t1 push --all /new branch/; /secret-1/; /secret-2/ push --tags /new tag/; /s1t1/; /s2t1/ " tsh " ## user u4 tries foo, fails, tries foo-pc cd $HOME/td clone u4:foo foo4; !ok /R access for foo DENIED to u4/ clone u4:foo-pc ; ok; /Cloning into 'foo-pc'/ /new branch.* dev/u1/foo .* dev/u1/foo/ /new branch.* master .* master/ /new branch.* next .* next/ /new branch.* secret-2 .* secret-2/ !/new branch.* secret-1 .* secret-1/ /new tag.* nt1 .* nt1/ /new tag.* s2t1 .* s2t1/ !/new tag.* s1t1 .* s1t1/ " tsh " ## user u4 pushes to foo-pc cd $HOME/td/foo-pc checkout master dc u4m1; dc u4m2; push; !ok /W refs/heads/master foo-pc u4 DENIED by fallthru/ /hook declined to update refs/heads/master/ /To u4:foo-pc/ /remote rejected/ /failed to push some refs to 'u4:foo-pc'/ checkout next dc u4n1; dc u4n2 push origin next; ok /To /home/gl-test/repositories/foo.git/ /new branch\] ca3787119b7e8b9914bc22c939cefc443bc308da -> br-\d+/ /u4:foo-pc/ /52c7716..ca37871 next -> next/ tag u4/nexttag; push --tags /To u4:foo-pc/ /\[new tag\] u4/nexttag -> u4/nexttag/ /\[new branch\] ca3787119b7e8b9914bc22c939cefc443bc308da -> br-\d+/ checkout master checkout -b dev/u4/u4master dc devu4m1; dc devu4m2 push origin HEAD; ok /To /home/gl-test/repositories/foo.git/ /new branch\] 228353950557ed1eb13679c1fce4d2b4718a2060 -> br-\d+/ /u4:foo-pc/ /new branch.* HEAD -> dev/u4/u4master/ " tsh " ## user u1 gets u4's updates, makes some more cd $HOME/td/foo git remote update /Fetching origin/ /From u1:foo/ /new branch\] dev/u4/u4master -> origin/dev/u4/u4master/ /new tag\] u4/nexttag -> u4/nexttag/ /52c7716..ca37871 next -> origin/next/ checkout master; dc u1ma1; dc u1ma2; /\[master 8ab1ff5\] u1ma2 at Thu Jul 7 06:23:20 2011/ tag mt2; push-om; ok checkout secret-1; dc u1s1b1; dc u1s1b2 /\[secret-1 5f96cb5\] u1s1b2 at Thu Jul 7 06:23:20 2011/ tag s1t2; push origin HEAD; ok checkout secret-2; dc u1s2b1; dc u1s2b2 /\[secret-2 1ede682\] u1s2b2 at Thu Jul 7 06:23:20 2011/ tag s2t2; push origin HEAD; ok push --tags; ok git ls-remote origin /8ab1ff512faf5935dc0fbff357b6f453b66bb98b\trefs/tags/mt2/ /5f96cb5ff73c730fb040eb2d01981f7677ca6dba\trefs/tags/s1t2/ /1ede6829ec7b75a53cd6acb7da64e5a8011e6050\trefs/tags/s2t2/ " tsh " ## u4 gets updates but without the tag in secret-1 cd $HOME/td/foo-pc git ls-remote origin; !/ refs/heads/secret-1/; !/s1t1/; !/s1t2/ /8ab1ff512faf5935dc0fbff357b6f453b66bb98b\tHEAD/ /8ced4a374b3935bac1a5ba27ef8dd950bd867d47\trefs/heads/dev/u1/foo/ /228353950557ed1eb13679c1fce4d2b4718a2060\trefs/heads/dev/u4/u4master/ /8ab1ff512faf5935dc0fbff357b6f453b66bb98b\trefs/heads/master/ /ca3787119b7e8b9914bc22c939cefc443bc308da\trefs/heads/next/ /1ede6829ec7b75a53cd6acb7da64e5a8011e6050\trefs/heads/secret-2/ /8ab1ff512faf5935dc0fbff357b6f453b66bb98b\trefs/tags/mt2/ /52c7716c6b029963dd167c647c1ff6222a366499\trefs/tags/nt1/ /01f04ece6519e7c0e6aea3d26c7e75e9c4e4b06d\trefs/tags/s2t1/ /1ede6829ec7b75a53cd6acb7da64e5a8011e6050\trefs/tags/s2t2/ git remote update /3ea704d..8ab1ff5 master -> origin/master/ /01f04ec..1ede682 secret-2 -> origin/secret-2/ /\[new tag\] mt2 -> mt2/ /\[new tag\] s2t2 -> s2t2/ !/ refs/heads/secret-1/; !/s1t1/; !/s1t2/ " echo DONE # last words... git ls-remote u4:foo-pc cd ~/gitolite-admin perl -ni -e 'print unless /marker 01/' conf/gitolite.conf git test 'add conf' 'commit -m erdel' 'ok' 'push -f' 'ok' git ls-remote u4:foo-pc cat >&2 < RANT sitaramc-gitolite-6857652/contrib/partial-copy/update.secondary000077500000000000000000000017461171610012500246070ustar00rootroot00000000000000#!/usr/bin/perl use strict; use warnings; # called from gitolite before any git operations are run # "we", "our repo" => the partial copy # "main", "pco" => the one which we are a "partial copy of" my $main=`git config --file $ENV{GL_REPO_BASE_ABS}/$ENV{GL_REPO}.git/config --get gitolite.partialCopyOf`; chomp ($main); exit 0 unless $main; 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 ($ref, $old, $new) = @ARGV; my $rand = int(rand(100000000)); $ENV{GL_BYPASS_UPDATE_HOOK} = 1; system("git", "push", "-f", "$ENV{GL_REPO_BASE_ABS}/$main.git", "$new:refs/heads/br-$rand") and die "FATAL: failed to send $new\n"; wrap_chdir("$ENV{GL_REPO_BASE_ABS}/$main.git"); system("git", "update-ref", "-d", "refs//heads/br-$rand"); system("git", "update-ref", $ref, $new, $old) and die "FATAL: update-ref for $ref failed\n"; exit 0; sitaramc-gitolite-6857652/contrib/putty.mkd000066400000000000000000000206241171610012500206630ustar00rootroot00000000000000# F=contrib_putty putty and msysgit This document is intended for those who wish to use Putty/Plink with msysgit. If you need more help with putty or component programs I suggest looking at [the official putty documentation](http://the.earth.li/~sgtatham/putty/latest/htmldoc/). **If you are not already using Putty for SSH it is recommended you do _NOT_ use it with msysgit.** **Please note that this only covers the client side of things, and does not involve server side components to troubleshooting. For that, please see the [ssh-troubleshooting document](http://sitaramc.github.com/gitolite/doc/ssh-troubleshooting.html).** ## msysgit setup Provided you have putty sessions msysgit should give you the option of specifying a location to plink. If it did not then you will need to add an environment variable named "GIT\_SSH" to point at plink.exe, wherever you have that sitting. How to do that on your version of windows will likely vary, and is not covered here. For purposes of example, on a 64 bit Windows Vista machine the GIT\_SSH value could be: C:\Program Files (x86)\PuTTY\plink.exe Note the lack of quotes. Testing that msysgit is properly configured can be done from the git bash shell. Simply type (case sensitive, include the quotes): "$GIT_SSH" -V You should get a response similar to this: plink: Release 0.60 If instead you get a "command not found" type error you likely have a typo in your environment variable. ## Going back to OpenSSH If you wish to go back to OpenSSH all you need to do is delete the GIT\_SSH environment variable. This will vary by your version of windows and thus is not covered here. ## Putty keys If you do not already have putty private key files (.ppk) you will need to make at least one. You can either make a new one or convert an existing key to putty private key format. Either way, you will want to use puttygen. Note that you can go the other way if you want to stop using putty but keep the key by exporting the key to OpenSSH format. ### Creating a new key To make it simple, I suggest SSH-2 RSA and a bit size of at least 1024. Larger keys will take longer to generate and will take longer to authenticate you on most systems. Making the key is as simple at hitting "Generate". It is recommended to give the key a meaningful comment. ### Importing an existing key If you already have an OpenSSH or ssh.com key you can import it using the "Import" option on the "Conversions" menu. If the key does not have a meaningful comment I would suggest adding one at this point. ### Loading an existing key If you need to load an existing key to edit or view it you can do so from the File menu. ### Public key To get your public key for use with gitolite, load (or generate, or import) your key into puttygen. There is a box labeled "Public key for pasting into OpenSSH `authorized_keys` file" there. Copy the text into your preferred text editor and save. ### Putty ageant Though not required in all cases you may wish to use the putty ageant, pageant, to load your key(s). This will allow for your key(s) to be passphrase protected but not have to enter the passphrase when you go to use them, provided you have already loaded the key into the ageant. ## Sessionless or raw hostname usage When using plink without a putty session you pretty much have to load your keys with putty ageant, if only so that plink can find them. ## Putty sessions In addition to hostnames msysgit can, when using putty, use putty sessions. This works in a manner similar to definitions in OpenSSH's `ssh_config` file. All settings in the session that apply to plink usage will be loaded, including the key file to use and even the username to connect to. Thus, instead of: ssh://user@host.example.ext:port/repo You can use: ssh://session_name/repo ## Host key authentication Whether you are using hostnames or sessions you still run into one potential problem. Plink currently wants to validate the server's SSH host key before allowing you to connect, and when git calls plink there is no way to tell it yes. Thus, you may get something like this: The server's host key is not cached in the registry. You have no guarantee that the server is the computer you think it is. The server's rsa2 key fingerprint is: ssh-rsa 2048 00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00 Connection abandoned. fatal: The remote end hung up unexpectedly Or, in the case of the host key changing, something like this: WARNING - POTENTIAL SECURITY BREACH! The server's host key does not match the one PuTTY has cached in the registry. This means that either the server administrator has changed the host key, or you have actually connected to another computer pretending to be the server. The new rsa2 key fingerprint is: ssh-rsa 2048 00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00 Connection abandoned. fatal: The remote end hung up unexpectedly The solution is to call plink directly, or start putty and connect with it first. To use plink, open the Git Bash shell and enter: "$GIT_SSH" hostname_or_session_name When you do you will see something like this: The server's host key is not cached in the registry. You have no guarantee that the server is the computer you think it is. The server's rsa2 key fingerprint is: ssh-rsa 2048 00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00 If you trust this host, enter "y" to add the key to PuTTY's cache and carry on connecting. If you want to carry on connecting just once, without adding the key to the cache, enter "n". If you do not trust this host, press Return to abandon the connection. Store key in cache? (y/n) Or, in the case of a changed key, a response like this: WARNING - POTENTIAL SECURITY BREACH! The server's host key does not match the one PuTTY has cached in the registry. This means that either the server administrator has changed the host key, or you have actually connected to another computer pretending to be the server. The new rsa2 key fingerprint is: ssh-rsa 2048 00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00 If you were expecting this change and trust the new key, enter "y" to update PuTTY's cache and continue connecting. If you want to carry on connecting but without updating the cache, enter "n". If you want to abandon the connection completely, press Return to cancel. Pressing Return is the ONLY guaranteed safe choice. Update cached key? (y/n, Return cancels connection) In either case hit y and the key will be stored. ## Debugging multiple putty ageant keys In the event you are using putty ageant with multiple keys loaded you may see the wrong key being used. In general, pageant keys are tried in the order they were loaded into the ageant. If you have descriptive comment on each of your keys you can try connecting with plink in verbose mode to see what keys are being tried. Simply open the Git bash shell and run: "$GIT_SSH" -v user@hostname Or, if using sessions with a pre-entered username: "$GIT_SSH" -v session_name In either case, you should look for lines like: Trying Pageant key #0 Authenticating with public key "My Key" from agent The first says which (numerical) key the ageant is trying. The second tells you the key comment for the authenticating key. To my knowledge the second line should only show up once, for the valid key. ## Setperms and other commands When using wildcard repos the setperms command is very important, and other commands can come in handy as well. See their documentation for how to use them, but where they use: ssh user@host command etc etc You will want to use: "$GIT_SSH" user@host command etc etc Otherwise everything should be identical. ## About this document This document was written by Thomas Berezansky (tsbere (at) mvlc (dot) org) in the hopes that it would be useful to those using putty on windows and wishing to use git/gitolite with their putty keys and sessions. sitaramc-gitolite-6857652/contrib/real-users/000077500000000000000000000000001171610012500210575ustar00rootroot00000000000000sitaramc-gitolite-6857652/contrib/real-users/gl-shell000077500000000000000000000061771171610012500225270ustar00rootroot00000000000000#!/usr/bin/perl use strict; use warnings; # ------------------------------------------------------------------------------ # BEGIN site-local changes # the original login shell your users had (or) the shell to forward # non-gitolite commands to my $shell = "/usr/bin/passwd"; # exceptions... my %shells = ( 'some.one' => '/bin/bash', ); # the gitolite host you want to forward git commands to. Typically this will # be 'git' or perhaps 'gitolite', but actually could be anything. Don't # forget to change the host part if needed and mind the quotes! my $gl_host = 'git@server2'; # ADCs... # either list all the ADCs you wish to allow forwarding to (SPACE-separated): my $ADC_list = ""; # -- OR -- # if you upgraded to the new 'help' adc with the '-list' option, set this to 1: my $detect_ADCs = 0; # if you do neither, ADCs are not forwarded # END site-local changes # ------------------------------------------------------------------------------ # change the user's default shell if he is an 'exception' $shell= $shells{$ENV{USER}} if $shells{$ENV{USER}}; # no arguments? nothing to forward exec($shell) if (not @ARGV and not $ENV{SSH_ORIGINAL_COMMAND}); # note: we attempt to work the same whether invoked via 'command=' of authkeys # (in which case SSH_ORIGINAL_COMMAND is set) or via us being the login shell # (chsh). Only the latter has been *tested* though. # massage SSHOC into @ARGV shape for ease of parsing @ARGV = ("-c", $ENV{SSH_ORIGINAL_COMMAND}) if $ENV{SSH_ORIGINAL_COMMAND}; # we ignore SSHOC from now on... # ------------------------------------------------------------------------------ # forward normal git ops forward(@ARGV) if $ARGV[0] eq '-c' and $ARGV[1] =~ /^(git-receive-pack|git-upload-pack|git-upload-archive) '(\S+)'$/ and ( not -d "$2" ); # ------------------------------------------------------------------------------ # forward gitolite special commands forward(@ARGV) if $ARGV[0] eq '-c' and $ARGV[1] =~ /^(info|expand|((set|get)(perms|desc)))( |$)/; # ------------------------------------------------------------------------------ # forward ADCs if ($ADC_list or $detect_ADCs) { $ADC_list ||= `ssh $gl_host help -list`; $ADC_list =~ s/\s+/ /g; # find the command he's running my $cmd = $1 if $ARGV[1] =~ /^(\S+)/; # forward if the command appears somewhere in the ADC list forward(@ARGV) if $ARGV[0] eq '-c' and $cmd and $ADC_list =~ /(^| )$cmd( |$)/; } # ------------------------------------------------------------------------------ # at this point it's back to local processing exec($shell, @ARGV); # ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------ # forward to the gitolite host sub forward { # this message is important in debugging and trouble shooting; see # documentation print STDERR "[forwarding to $gl_host]\n"; # but first we check for rsa key -f ".ssh/id_rsa" or die "ask your admin to add you to gitolite"; shift if $_[0] eq '-c'; exec("ssh", "$gl_host", @_); } sitaramc-gitolite-6857652/contrib/real-users/gl-shell-setup000077500000000000000000000041011171610012500236460ustar00rootroot00000000000000#!/bin/bash # WARNING 1: probably contains bashisms galore. If you don't have bash, # please install it. # NOTE 1: this script is run as root. # ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------ # BEGIN site-local changes # the full path to the new login shell to replace these users' existing shell new_shell="/usr/local/bin/gl-shell" my_chsh() { # please replace with appropriate command for your OS/distro. This one is # suitable at least for Fedora, maybe others also chsh -s $new_shell $1 >&2 } # remove these 2 lines after you have done your customisation [ -f /tmp/done.gl-shell-setup ] || { echo please customise $0 before using >&2; exit 1; } # END site-local changes # ------------------------------------------------------------------------------ die() { echo "FATAL: $@" >&2; exit 1; } # ------------------------------------------------------------------------------ euid=$(perl -e 'print $>') if [ "$euid" = "0" ] then [ -n "$1" ] || die "need a valid username" user=$1 id $user >/dev/null || die "need a valid username" # now fix up the user's login shell my_chsh $user pubkey="$PWD/$user.pub" [ -f "$pubkey" ] && { echo "$user.pub already exists. Shell changed, exiting..." >&2 exit 0 } # drat... 'cd ~$user` doesn't work... cd $(bash -c "echo ~$user") || die "can't cd to $user's home directory" # now set up her rsa key, creating it if needed. This will get used if # she comes in via password or without agent forwarding. [ -d .ssh ] || { mkdir .ssh chown $user .ssh chmod go-w .ssh } [ -f .ssh/id_rsa.pub ] || { ssh-keygen -q -N "" -C $user@`hostname` -f .ssh/id_rsa >&2 chown $user .ssh/id_rsa .ssh/id_rsa.pub chmod go-rw .ssh/id_rsa chmod go-w .ssh/id_rsa.pub } # create alice.pub cat .ssh/id_rsa.pub > $pubkey exit 0 else die "needs to run as root" fi sitaramc-gitolite-6857652/contrib/real-users/password-access.mkd000066400000000000000000000100631171610012500246550ustar00rootroot00000000000000# F=password_access password access to gitolite (a.k.a: turning real users into gitolite users) ## problems This document solves several different problems. But first some names: * `alice`: our user * `server1`: a server on which alice has a shell account or the admin is willing to give her one * `git@server2`: the gitolite host (user@server). Server2 may be, but need not be, the same as server1. The problems it solves are: 1. Alice doesn't like ssh keys and wants to stick to password access (which won't work with gitolite!), and she has or can get a real (unix) userid on server1. 2. Alice is outside your corporate environment and needs to get in to server2 via server1. It does this by making `alice@server1` act like a "proxy" for `git@server2`. ## what the 2 scripts actually do * `gl-shell` will become the login shell for these users on server1. This shell will forward git clone/fetch/push requests to "git" on server2. This redirection is so transparent that I had to explicitly code a message ("forwarding to git@server") to make troubleshooting easier. * `gl-shell-setup` sets things up. It needs to be run on server1, where it changes the user's shell to `gl-shell` (full path), then sets up an RSA key if needed. ## instructions ### server setup **Server1**: * Add the host key for server2 to `/etc/ssh/ssh_known_hosts` on server1. And if it ever changes, update it. ssh-keyscan -t rsa,dsa server2 >> /etc/ssh/ssh_known_hosts * You will need to copy the 2 scripts supplied (in contrib/real-users) to "/usr/local/bin" on server1 and customise them -- i.e., edit the files and change stuff in the section clearly-marked "site-local changes". **NOTE** on fixing the "chsh" function: this is OS-dependent. Use whatever command the OS on server1 requires for this to work. The supplied command is good for Fedora. (Server2's OS does not matter for this customisation, even though the script is needed there also). **Server2**: We assume gitolite is already installed on server2. ### per-user setup There are 2 types of users. 1. Alice uses a password to access server1, or Alice uses a pubkey to access server1 but does not use an agent or enable agent forwarding. * login as root on server 1. * make sure a file called 'alice.pub' does NOT exist in the current directory. * run `gl-shell-setup alice`. This will create a file called 'alice.pub' (since it does not exist). 2. alice uses a pubkey to access server1, and does agent forwarding. * login as root on server 1. * ask the user for the pubkey she uses or get it from the `authorized_keys` file in her `$HOME/.ssh` on server1. Copy it as 'alice.pub' in the current directory. * run `gl-shell-setup alice`. (It will not create the pub file, since it already exists). You can do this for several users in one shot, and collect all the the pubkeys. Once you have collected all of them, send them to server2 or to someone who has push rights to the admin repo. ## some hints, notes and caveats This doesn't mean all your users have to be like this. You can have normal users also. In fact, you can have users who give you a pub key from their workstation the normal way, as well as use this method. (I strongly suggest that all such keys be placed in `keydir/indirect/` instead of in `keydir/`). ### security and trust discussion For type 1 users, a default key *pair* (i.e., *including* a private key) must be generated on server1 and given to the gitolite admin for inclusion as the user's key for gitolite. This means that the user must implicitly trust server1's administrators, since they can impersonate her to server2. (If server1 and server2 are the same machine or have the same administrators, this does not matter). For a type 2 user, no keys need to be generated, and this type of user need not trust server1 at all. In fact, she's only using this method due to firewall issues, and the only thing that gl-shell-setup is doing is to run 'chsh'. sitaramc-gitolite-6857652/contrib/update.detect-dup-pubkeys000077500000000000000000000050601171610012500237230ustar00rootroot00000000000000#!/bin/bash # update "hooklet" to detect duplicate public keys # This particular hooklet also serves as an example for people writing others. # [It should be quite easy to figure out what parts apply to any hooklet and # what parts are specific to *this* hooklet and its function.] # see hooks/common/update.secondary.sample for instructions on *enabling* # hooklets # a hooklet is called as follows: # git-receive-pack --> 'update' --> 'update.secondary' --> this script # note: the same three arguments that git passes to the update hook are passed # along to each hooklet. # the update hook, and therefore the hooklets, are called for *every* repo out # there. If you want this hooklet to run only for certain repos, here's how: [ "$GL_REPO" = "gitolite-admin" ] || exit 0 # superfluous, since update.secondary already did it, but I'd like to # emphasise that all output MUST go to STDERR exec >&2 # ---- # the main functionality of the hooklet starts here. In this one (and I # suspect many others) we want to examine the actual files from the commit # that was pushed. # get the tip commit being pushed sha=$3 # git sets this; and we don't want it at this point... unset GIT_DIR # paranoia set -e # setup the temp area export TMPDIR=$GL_REPO_BASE_ABS export tmp=$(mktemp -d -t gl-internal-temp-repo.XXXXXXXXXX); trap "rm -rf $tmp" EXIT; # now get the files into $tmp. # (note: if your task does not require the actual files, and you can # manage with "git cat-file -s" and so on, then you may not even need a # $tmp; you may be able to do it all right in the repo.git directory) git archive $sha keydir | tar -C $tmp -xf - # DO NOT try, say, 'GIT_WORK_TREE=$tmp git checkout $sha'. It'll screw up # both the 'index' and 'HEAD' of the repo.git. Screwing up the index is # BAD because now it goes out of sync with $GL_ADMINDIR. Think of a push # that had a deleted pubkey but failed a hooklet for some reason. A # subsequent push that fixes the error will now result in a $GL_ADMINDIR # that still *has* that deleted pubkey!! # And this is equally applicable to cases where you're using a # post-receive or similar hook to live update a web site or something, # which is a pretty common usage, I am given to understand. cd $tmp # ---- # *finally*, the actual check you need to do in this hook: look for duplicate # pubkeys and exit 1 if dups are found for f in `find keydir -name "*.pub"` do ssh-keygen -l -f "$f" done | perl -ane ' die "$F[2] is a duplicate of $seen{$F[1]}\n" if $seen{$F[1]}; $seen{$F[1]} = $F[2]; ' sitaramc-gitolite-6857652/contrib/update.email-check000077500000000000000000000053741171610012500223570ustar00rootroot00000000000000#!/usr/bin/perl # Technical notes: # Gitolite specific script to check "author email" field of every commit # pushed and to disallow if this email does not match the email that the # user pushing is expected to have. # Use without gitolite is also possible; just substitute your access # control system's notion of "user" for the env var GL_USER in the code # below and probably call it "update" if you dont already have an update # hook. # Mapping between "username" and "email address" is encapsulated in a # subroutine for ease of changing; see code below. # Philosophical notes: # Doing this breaks the "D" in "DVCS", forcing all your developers to work # to a centralised model as far as pushes are concerned. It prevents # amending someone else's commit and pushing (this includes rebasing, # cherry-picking, and so on, which are all impossible now). It also makes # *any* off-line collabaration between two developers useless, because # neither of them can push the result to the server. # PHBs should note that validating the committer ID is NOT the same as # reviewing the code and running QA/tests on it. If you're not # reviewing/QA-ing the code, it's probably worthless anyway. Conversely, # if you *are* going to review the code and run QA/tests anyway, then you # don't really need to validate the author email! # In a DVCS, if you *pushed* a series of commits, you have -- in some # sense -- signed off on them. The most formal way to "sign" a series is # to tack on and push a gpg-signed tag, although most people don't go that # far. Gitolite's log files are designed to preserve that accountability # to *some* extent, though; see contrib/adc/who-pushed for an admin # defined command that quickly and easily tells you who *pushed* a # particular commit. # Anyway, the point is that the only purpose of this script is to # - pander to someone who still has not grokked *D*VCS # OR # - tick off an item in some stupid PHB's checklist use strict; use warnings; # mapping between gitolite userid and correct email address is encapsulated in # this subroutine; change as you like sub email_ok { my ($author_email) = shift; my $expected_email = "$ENV{GL_USER}\@atc.tcs.com"; return $author_email eq $expected_email; } # print STDERR "SECONDARY HOOK:\n" . join(",", @ARGV, "\n"); my ($ref, $old, $new) = @ARGV; for my $rev ( `git log --format="%ae\t%h\t%s" $new --not --all` ) { chomp($rev); my ($author_email, $hash, $subject) = split /\t/, $rev; die "$ENV{GL_USER}, you can't push $hash authored by $author_email\n" . "\t(subject of commit was $subject)\n" unless email_ok($author_email); } exit 0; sitaramc-gitolite-6857652/contrib/vim/000077500000000000000000000000001171610012500175705ustar00rootroot00000000000000sitaramc-gitolite-6857652/contrib/vim/README-vim.mkd000066400000000000000000000004671171610012500220220ustar00rootroot00000000000000# F=vimsyntax_ Vim Syntax Highlight [Vim][] Syntax highlight for `gitolite.conf` can be found from: - [vim.org script page][vim.org] (Releases) - [GitHub][] (Sources) [Vim]: http://www.vim.org/ [vim.org]: http://www.vim.org/scripts/script.php?script_id=2900 [GitHub]: http://github.com/tmatilai/gitolite.vim sitaramc-gitolite-6857652/doc/000077500000000000000000000000001171610012500161025ustar00rootroot00000000000000sitaramc-gitolite-6857652/doc/1000-words.mkd000066400000000000000000000231721171610012500203200ustar00rootroot00000000000000# F=pictures gitolite in pictures Well, they say a picture speaks a thousand words, so here're a few! **NOTE**: if you're viewing this file in raw text, please note that some characters in text within a ditaa drawing may not be ASCII. This is due to a ditaa flaw that treats even a single hyphen as a line drawing character, so I had to use Unicode 2010 for it. I expect that I will have to resort to similar tricks for colon, equals, and many others like it if and when I need those in text within a ditaa diagram. ## installation and setup Here's a picture showing the "non-root" install. We assume Alice is the gitolite admin, and "git" is the hosting user on the server. .aa Gitolite install and setup sequence (non_root method, default values) ------------------------------------------------------------------------ /-----------------\ /----------------------\ | user "alice" | | user "git" | |(on workstation) | | (on server) | |cPNK | |cGRE | \-----------------/ \----------------------/ /-----------------\ /----------------------\ |~/.ssh/id_rsa.pub|------->| alice.pub |-----\ \----------+------/ (1) \----------------------/ | /----------------------\ | /-------------->| ~/gitolite |-----+ | (2) /-----| | | | | \----------------------/ | | | /----------------------\ | | | | ~/bin |-----+ | \---->| ~/share | | | (3) \----------------------/ | /----------+------\ /----------------------\ | | (github) | | ~/.gitolite.rc | | |cBLU | |~/.ssh/authorized_keys|<----/ \-----------------/ | ~/.gitolite | (4) | ~/repositories | \----------------------/ The files names are there **only for information**. You do **not** have to do anything to them yourself; in fact you should not! You only need the command for each step shown: 1. copy the admin's pubkey to the server as "alice.pub" 2. `git clone git://github.com/sitaramc/gitolite` or equivalent 3. `gitolite/src/gl-system-install` 4. `gl-setup -q alice.pub` Note also that you only need ONE real user on the server. In our example it is git. In particular, you do NOT create Unix userids for your gitolite users. ## adding users to gitolite Once you've done the install, here's how you add users. .aa Adding users to gitolite ------------------------------------------------------------------------ /-------------------\ /-----------------------\ | user "git" | | user "alice" | | (on server) | | (on workstation) | | cGRE | | cPNK | \-------------------/ \-----------------------/ /-------------------\ /-----------------------\ | (gitolite) |----------->| ~/gitolite‐admin | | | (1) | | | | | | | |<-----------| | \-------------------/ (3) \-----------------------/ /-----------------------\ /-------------------\ | (alice@workstation) | | bob cYEL |----\ |~/gitolite‐admin/keydir| | ~/.ssh/id_rsa.pub | | |cPNK | \-------------------/ | +-----------------------+ \------>| bobby.pub | /-------------------\ (2) +-----------------------+ | carol cYEL |----------->| carol.pub | | ~/.ssh/id_rsa.pub | +-----------------------+ \-------------------/ | (...) | \-----------------------/ All this is done from the admin (Alice)'s workstation. The steps are: 1. `git clone git@server:gitolite-admin` 2. obtain pubkeys from each user. Email, USB, DHL, pigeon post, owl mail, any method you like. Rename each received file to the name of the user, add a ".pub" at the end, copy it into `keydir/` in the gitolite-admin repo you cloned. 3. `git add keydir`, then `git commit`, then `git push` You do NOT need to add Carol or Bob as *real* (Unix) users. You do NOT add their keys directly anywhere on the server; you do it by cloning, adding keys, and pushing. ## adding repos to gitolite Adding a repo is even easier. It's so easy that you don't really need a picture. OK maybe a small one: .aa Adding repos to gitolite ------------------------------------------------------------------------ /-------------------\ /-----------------------\ | user "git" | | user "alice" | | (on server) | | (on workstation) | | cGRE | | cPNK | \-------------------/ \-----------------------/ /-------------------\ /-----------------------\ | (gitolite) |----------->| ~/gitolite‐admin | | | (1) | | | | | | | |<-----------| | \-------------------/ (3) \-----------------------/ /-----------------------------------\ | (alice@workstation) | |~/gitolite‐admin/conf/gitolite.conf| | cPNK | +-----------------------------------+ | | +-----------------------------------+ | | +-----------------------------------+ | (...) | \-----------------------------------/ Again, all this is done from the admin (Alice)'s workstation. Steps one and three are the same as for adding users, but step 2 consists of adding config lines for whatever repo you want too add. 1. `git clone git@server:gitolite-admin` 2. edit `conf/gitolite.conf` in the repo clone you just made. Add repo paragraphs, maybe like this, and save the file: repo foo RW+ = alice RW = bob 3. `git add conf/gitolite.conf`, then `git commit`, then `git push` You do NOT add the repos directly anywhere on the server; you do it by cloning, adding keys, and pushing. ## #flow gitolite flow This is the overall flow of gitolite, showing how the various parts fit together. This is particularly useful for an admin to see where his site-local hooks fit, how they get called, and what stages of the process they affect. **Legend**: diamonds are decision boxes whose results can abort the operation. Arrows are calls/invocations. Dashed lines just point to sub-parts of the process on the left side. Blue processes are external to gitolite. Green is gitolite code. Yellow is site-local code that you (the admin of your site) can add, to influence gitolite's behaviour. Authentication (**AUTHN**) is typically done by sshd, but could also be httpd etc. This step invokes `gl-auth-command` (**GLAC**), which is the main entry point for gitolite, passing it a username and a command. The most common commands look like one of these: git-upload-pack 'repo' git-receive-pack 'repo' GLAC first checks the command to see if it is a read or a write (**RW**) operation (upload-pack is a read, receive-pack is a write). At this point, GLAC knows the username, the reponame, and the type of operation. It executes the first [level][2levels] access check (**AC1**), which passes if the user has at least one ref for which the operation is allowed. Note that neither "deny" rules nor the rule sequences are taken into account for this step. For instance, user "alice" will pass this step even with this configuration: repo foo - = alice RW = alice although some other user, say "bob", will not. .gv "Gitolite Flow" rankdir=LR edge[dir=forward] [b] AUTHN [g] GLAC [g] RW <> [g] AC1 <> [y] PGH [b] Git [g] UP <> [g] AC2 AUTHN .. GLAC GLAC -- RW GLAC -- AC1 GLAC .. PGH GLAC .. Git .. UP UP -- AC2 <> [y] SEC UP .. SEC Once AC1 has passed, GLAC calls the [`gl-pre-git`][pre-git] hook (**PGH**), if it is present. This hook allows the admin to add his own checks at this first stage and even abort the operation if needed. If PGH passes, GLAC now invokes git itself, which runs the command specified. If the command is `git-upload-pack`, nothing else happens and the process completes. However, if it is a push operation (`git-receive-pack`) then git calls the update hook (**UP**), per `man githooks`. The update hook executes the second [level][2levels] access check (**AC2**), and depending on its success, the operation succeeds or fails. The update hook calls a secondary hook, [`update.secondary`][hookchaining] (**SEC**) if it exists. Similar to PGH, this allows the admin to add site-local checks before allowing the update to complete. sitaramc-gitolite-6857652/doc/CHANGELOG000066400000000000000000000242721171610012500173230ustar00rootroot00000000000000Major changes to gitolite, master branch only, most recent first, no dates but the tags can help you position stuff approximately [NYD = not yet documented due to lack of time...] - v2.3 - added "M" permission to allow enforcing a "no-merges" policy. - gl-setup is a little more puppet-friendly. To be specific, you can now manage gitolite without having gitolite-admin as a repo, such as can happen with puppet and such. The commit message for d08aca6 has details and a script that may help you if you're doing this. - v2.2.1 (caused by minor bug re not handling blank lines in authkeys file) - ADCs gained new functions can_{read,write,create}, is_admin, and in_group. - v2.2, gpg signed :-) - major change to the online rendered docs to make them more readable - (lots of nice documentation additions: gitolite in pictures, "user" manual, gitolite.conf by example, ...) - (minor backward compat breakage) if you're using svn or rsync support. You can still use them but they work differently now. - git-annex support via unrestricted ADC (i.e., whose arguments are NOT checked by gitolite) - eliminated almost all hardcoding in test suite. This helped create a "playing with gitolite" mode for "try before install" scenarios. - deny rules for the first level access check! - v2.1 - 'symbolic-ref' ADC replaces and obsoletes 'set-head' - BIG CHANGE: easy-install ('from-client' method) is now HISTORY from the docs and code. Upgrade to one of the other methods. Look for a section on "upgrading from from-client method to non-root method" in the docs - nice new "password access" mode for people who can't grok ssh keys - new program gl-dryrun, to be run in any admin repo clone, to check syntax errors etc before you actually push - very complex mirroring setups now have a helper script to setup - behanw contributed a better converter for gitosis - the mirroring stuff is just out of this world now... - new 'subconf' feature to explicitly delegate; the implicit one is now legacy, but is backward compatibly handled so you don't need to fix your config - delegation now allows includes in fragments -- no idea why we first disallowed this - fix accumulation and sequencing of git config lines; very important for mirroring (coming up) - v2.0.3 - gl-dont-panic replaced by more generic gl-admin-push - v2.0.2 - self-service key management - (from-client install method is now loosely deprecated) - su-setperms and su-getperms, like su-expand -- allow an admin to get/set perms on behalf of some user - 'delete-branch' ADC -- this is the most practical way to allow someone to delete a branch that they themselves created; see commit message for 89b68bf - v2.0.1 - "hooklets" allow you to have any number of site-local 'update hook' scripts play nicely with gitolite - the "include" statement can now use globs -- makes some types of setups sooooo easy! - mirroring finally tested properly after v2 - 'hub' adc (pull requests etc) - s3backup ADC contributed by Bremner - warn() and die() are now logged - v2.0 - gl-pre-git hook added - 'hub' adc added - v2.0rc2 *** V1.5.9.1 -- IMPORTANT SECURITY FIX; PLEASE UPGRADE IF YOU'RE USING SOMETHING OLDER THAN THIS ONE - supercool new 'git' adc; so cool it's disabled by default - '-prune' added to find commands; makes a big diff if REPO_BASE is NFS/CIFS mounted - first python ADC contributed -- 'perms' makes setperms etc easier to do - 'set-head' ADC added - v2.0rc1; major refactor - v1.5.9 - Nokia MeeGo team contributed ldap scripts - large configs should now be twice as fast (except when gl-perms exists) due to my finding and eliminating a wasted parse_acl - major change: split the config file when in big-config mode (includes a data format change) - GL_ALL_READ_ALL to make things much, (MUCH!) faster for sites where all can read all repos (like Fedora) - rc file revamp -- was getting too big and unwieldy; now the documentation is in a new file instead of inline - allow gitolite to be used even when users have real IDs (thus $HOME is not valid to find the rc file); allow /etc/gitolite/gitolite.rc then - BIG one for adc writers -- full blown access checks (ref level) can be done from an ADC now (though it has to be in perl, not shell)! - allow full access checks from perl (shell can only do level 1 checks); useful in hooks or ADCs - v1.5.8 - Jeff from KDE contributed the watch ADC - allow disabling gitolite (write access only) to take backups - document how to move gitolite from one server to another - custom permissions categories (default being READERS and WRITERS) for setperms (thanks to Jeff from KDE for the idea/need) - v1.5.7 - "help" adc can allow site local help text to be shown - "who-pushed" adc to find out who pushed a given commit - rmrepo adc replaced by lock/unlock/rm and trash/list-trash/restore families of commands - v1.5.6 - new method for passing usergroup info (minor backward compat breakage); see commit message and doc/big-config.mkd for more on this - added "sudo" (adc) - added "gl-reflog" (adc) to get a fake reflog from the server! - added support for a post-repo-create hook called "gl-post-init" - (BIG ONE!) SMART HTTP SUPPORT! - @all can now include gitweb/daemon also (default, not include) - allow @groups in setperms - gitweb/daemon now work within setperms - log elapsed time (optional) - more than one wildcard may match a repo, plus it can also be matched by a normal repo line - test suite has lots of new tests for the below - (big change) all combinations of wild repos and big configs, including daemon/gitweb/git-config settings, should work now! - v1.5.5 - mirroring support - setup_authkeys is now separate; can be called from outside also; useful for people who want to maintain ssh keys via LDAP or something, and not within gitolite - (two months too late for towel day) gl-dont-panic! [replaces the old "gl-emergency-addkey" program. It does more (including recovering from a botched push, not just lost keys), is cleaner, and works for all install methods] - document on how to create a mob branch - info command now takes a parameter to limit output; this is mandatory if GL_BIG_CONFIG is on - v1.5.4 - new RC variables: GL_NO_CREATE_REPOS and GL_NO_SETUP_AUTHKEYS (inspired by the specific needs that Fedora have, but made as generic as possible) - separating push branch rights from create branch rights changed to use the same mechanism as the (older) mechanism for separating rewind from delete - v1.5.3 - log file format changed; minor backward compat breakage if you've been doing any automated log processing - some small but important doc updates - adc "fork" now much faster and more space-efficient (uses git clone -l) - v1.5.2 - added test suite - v1.5.1 - disallow creation of new refs if you want (while allowing push of the same) - adc "able" command added to contrib - easy-install now takes a host-nickname parameter for convenience in installing more than one gitolite server - major doc revamp; contrib/autotoc added to make docs look nicer - eliminate the need to run gl-setup on data version change, thus hopefully obsoleting the upgrade note for v1.5 (just below). - v1.5 -- IMPORTANT UPGRADE NOTES below Upgrading to v1.5 from any version prior to v1.5 requires an extra step for people who installed gitolite using the "system install / user setup" method described in doc/0-INSTALL.mkd. For such installations, after the administrator has upgraded gitolite system-wide, each "gitolite host" user must run `gl-setup` once (this time without any arguments). - "deny" rules should now work even in "big-config" due to previous change - proper rule sequencing (required major format change) - allow usergroup info to be passed in from outside, say via LDAP; see doc/big-config.mkd for details - (new) big-config is now part of mainline (old one had bitrotted); see doc/big-config.mkd for details - gl-system-install: help people simulate an RPM/DEB install by just running that commmand with appropriate arguments; see doc/0-INSTALL.mkd - admin-defined commands; see doc/admin-defined-commands.mkd - v1.4.2 (prep for major refactor on rights queries - v1.4.1 (security fix) - REFUSE TO RUN ON SERVER GIT < 1.6.2 (do NOT upgrade gitolite to or beyond this point if you are unable to upgrade git itself to at least 1.6.2) - "D" must be combined with RW or RW+ (warning: minor backward compat breakage) - v1.4 - recurse through keydir for pubkeys - bypass update hook if GL_BYPASS_UPDATE_HOOK is available in ENV - new server-side program "gl-tool", subcommand "shell-add" - new "D" permission (makes RW+ no longer imply "D" if used) - @all for repos is now a true @all - allow setperms to specify @all - post-update hook and gl-setup should be dash compat now - workaround for a Data::Dumper crash; see 412a691 - both hooks chain to ".secondary" now - new style personal branches (see 2456cc1 for advantages) - v1.3 - easier to move repos into gitolite - pattern for expand is no longer anchored - v1.2 - distro packaging support -- easy to install systemwide now - v1.1 - contrib directory added - expand now lists non-wildcard repos also - refs also have groups now - allow admins to get "info" for other users - wildrepos merged - getdesc and setdesc for wildrepos - htpasswd subcommand - access control for rsync - v1.0 - sshkeys-lint program added, doc/6 revamped - @SHELL in config changed to $SHELL_USERS in rc - "include" mechanism - delegation now uses NAME/ instead of branches - PATH/ changed to NAME/ - @SHELL in config - use of @all for repos also (see doc for caveat) - config entries for repos - deny rules (no more "rebel" branch!) - PATH/ - specify gitweb owner - v0.95 - easy install can run from msysgit also - v0.90 - allow admin defined hooks - specify gitweb desc - v0.85 - emergency addkey program - v0.80 sitaramc-gitolite-6857652/doc/COPYING000066400000000000000000000431031171610012500171360ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. sitaramc-gitolite-6857652/doc/admin-defined-commands.mkd000066400000000000000000000203471171610012500230700ustar00rootroot00000000000000# F=ADCs admin defined commands ## ADC background The admin-defined commands (ADCs) feature allows controlled access to specific, "safe", programs or scripts, without giving users full shell access. **WARNING**: regardless of what you read below, the security of the code in the commands or scripts you install as ADCs is **your responsibility**. The sample ADCs shipped with gitolite (in `contrib/adc`) should be safe enough, but an extra pair of eyes never hurt, so please review before use. Although this is a generic way to allow pretty much any command to be run, most of the examples and sample ADCs pertain to allowing users to manage their "own" repos. If that's your use case, please read the [wildcard repositories][wild] doc before you continue here. ## ADC details ### installing ADCs ADCs can only be installed by someone with shell access to the server; merely having push rights to the admin repo is not enough. * edit `~/.gitolite.rc` and set `$GL_ADC_PATH` to a directory that is *not* in `$PATH`. * add your "safe" executables to this directory. **Warning**: An ADC can hide (or override) gitolite's built-in commands like 'info', 'expand', 'setperms', or even 'git-receive-pack' or 'git-upload-pack'! This is by design. So be careful what you name your scripts. However, it is perfectly ok, and may even be necessary in some cases, to name them after system executables (like 'rsync'). ### user invocation If you have a command called "foo" in that directory, then a user can invoke it by saying: ssh git@server foo argument list ### checking authorisation inside an ADC Once an ADC is installed, *all* users can run it. But sometimes you want only some people to be able to do so. While you cannot prevent the ADC from running at all, you can *start* the ADC with code that checks the user's access to *any* arbitrary repo. For example, you can bail out if the user does not have "W" access to the "gitolite-admin" repo, which is an easy way of making sure an ADC is only run by admins. See the section on "the anatomy of a command" later for this and many more details. ### checking arguments Gitolite will call an ADC only if the arguments passed to it match a very strict pattern (see `$ADC_CMD_ARGS_PATT` in `src/gitolite_rc.pm`). This reduces the risk of various kinds of shell-meta related compromises. ### passing unchecked arguments Some commands need arguments with a broader range of characters than `$ADC_CMD_ARGS_PATT` will allow. As long as you are sure those commands are doing their own argument checking and sanitisation, you can place such commands in `$GL_ADC_PATH/ua` and they will be run with **no checks on the arguments**. The "ua" stand for "unchecked arguments". Consider this your last warning ;-) ## "fake" repos and access control for non-git programs A "fake" repo is a repo that exists in the config file but is specially named (starts with "EXTCMD/") so that gitolite will not create an actual repo on disk for it. It serves as a place holder for different sets of rules. If you install the 'rsync' ADC, you can use a fake repo called 'EXTCMD/rsync' to collect a set of rules that specify what user is allowed to read/write what files using the rsync command on his workstation. See `contrib/adc/rsync` for more on this. *Any* non-git program can be similarly access controlled, as long as the *command line* that the client attempts to execute on the server has sufficient information to decide. Protocols where the command line is just one word and everything else happens in the conversation later cannot be helped by this mechanism. ## anatomy of a command You can do whatever you want in an ADC! It's upto you to check the permissions of *each* repo that the user is manipulating using your ADC -- your code can `rm -rf $GL_REPO_BASE_ABS` if you like and gitolite wouldn't stop you. The current directory (`$PWD`) will be set to the `$HOME` of `git@server` (or whatever id you're using). It won't be any specific repo, it won't even be the base directory of all the repos. Gitolite defines a few environment variables, as well as allows you to directly query the ownership and access permissions of any repository. The environment variables available are: * `GL_USER` -- the name of the user invoking the command * `GL_BINDIR` -- the directory containing all the binaries (in particular, `gitolite.pm`, which is all we really care about here) * `GL_REPO_BASE_ABS` -- the absolute path of the base directory containing all the repos There are a few other variables also available but the above are the only ones you should rely on. Please treat any other variables you notice as being internal/undocumented/subject to change. [Implementation note: some of the distro packagers don't seem to like `GL_BINDIR`. I have not tested this in those scenarios, but they probably put `gitolite.pm` somewhere in perl's lib path anyway, so it ought to work]. You can 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$GL_BINDIR -Mgitolite -e "cli_repo_rights('reponame')" which will print two space-separated words: permissions and owner. Something like `_____R__W u1` or maybe `____@R_@W `. (The `u1` indicates the queried repo is a wildcard repo created by user `u1`; for meanings of the "@" see doc/report-output.mkd) But that's cumbersome. It's much nicer to use the convenient functions defined in `contrib/adc/adc.common-functions`; see the comments in that file for details, and any of the other samples for how to use them. If you prefer perl, there is a nicely commented example in `contrib/adc/get-rights-and-owner.in-perl`. ## example uses and sample commands in `contrib/adc` ### #fork the 'fork' ADC A user would use the fork command like this: ssh git@server fork from to where "from" is a repo to which the user invoking the fork has "R" access, and "to" is a repo that does not yet exist and to which he has "C" access. (Reminder: these access checks are done by the "fork" script, **not** within gitolite -- once again, you are responsible for making sure your scripts maintain the security of the system!) Strictly speaking this command is not really needed. Even without all this "admin-defined commands" setup you could still do the following, purely from the client side: git clone git@server:from cd from git remote add new git@server:to git push new refs/*:refs/* or some such incantation. ### deleting/trashing repos See the [repo-deletion document][wild_repodel] for details about this. ### #able enable/disable push access temporarily If you want to disable push access to gitolite temporarily (maybe for maintenance), anyone with write access to the gitolite-admin repo can do this: ssh git@server able dis @all # able dis ==> dis able To re-enable after the maint work is done: ssh git@server able en @all # able en ==> en able You can also do this for one or more individual repos; in place of `@all`, just use a space separated list of reponames (exactly as they would appear in the config file). Wildcards are not supported; patches welcome ;-) Note: please see [this][disable] for more on this. ## how the ADC feature came about Gitolite was named to be short for "gitosis-lite". Someone now wants to turn it into a "github-lite" :-) and even had some code to start me off thinking. Since my first impulse on being asked for a feature is to say no, I was casting about for a reason when he gave me one: he first made some noises about perl, then said something about rewriting it all in scheme. Nice... I resisted the urge to point him to [this][xkcd224], told him that's a great idea and he should go for it, mentally blessing him for letting me off the hook on coding it ;-) [Laziness][lazy] *is* the first virtue you know! [xkcd224]: http://xkcd.com/224/ [lazy]: http://c2.com/cgi/wiki?LazinessImpatienceHubris And that was that. For a couple of days. Soon, though, I realised that there could be a pretty big bonus in this for tightly controlled setups, so I went and coded it all anyway. See the section on "restricted admin" for what's really exciting about this for *me*. sitaramc-gitolite-6857652/doc/admin.mkd000066400000000000000000000363721171610012500177020ustar00rootroot00000000000000# F=admin administering and running gitolite ## please read this first Unless you know what you're doing, do not do **anything** manually on the server (except when the documentation says you should, for example to add custom hooks). In particular, adding new repositories or users or changing the access control rules should not be done directly on the server. Things will break. For example, if you manually create a repo on the server, it will not have the required "update" hook, without which there is no access control for pushes. Most normal (day-to-day) gitolite admin work is done by cloning the gitolite-admin repo from the server to your workstation, making changes to the clone, and pushing those changes back. The installation steps in the previous section include the steps to do this clone, so you should already have one on your workstation, in `~/gitolite-admin`. You can of course clone it anywhere else you want and use that clone. Either way, make sure you `cd` into this clone first. *Note*: some of the paths in this document use variable names. Just refer to `~/.gitolite.rc` for the correct values for *your* installation. Once you've cloned it, you're ready to add users and repos. ## F=add adding users and repos Do **NOT** add repos or users directly on the server! You MUST manage the server by cloning the special 'gitolite-admin' repo on your workstation (`git clone git@server:gitolite-admin`), making changes, and pushing them. This section tells you how to add users and repos. * ask each user who will get access to send you a public key. See other sources (for example [here][genpub]) for how to do this * rename each public key according to the user's name, with a `.pub` extension, like `sitaram.pub` or `john-smith.pub`. You can also use periods and underscores * copy all these `*.pub` files to `keydir` in your gitolite-admin repo clone. You can also organise them into various subdirectories of `keydir` if you wish, since the entire tree is searched. * edit the config file (`conf/gitolite.conf` in your admin repo clone). See the [gitolite.conf][conf] documentation for details on what goes in that file, syntax, etc. Just add new repos as needed, and add new users and give them permissions as required. The users names should be exactly the same as their keyfile names, but without the `.pub` extension * when done, commit your changes and push. Any new repos you specified will automatically be created (empty, but clonable) and users' access will be updated as needed. [genpub]: http://sitaramc.github.com/0-installing/2-access-gitolite.html#generating_a_public_key ## F=hooks using hooks ### #customhooks custom hooks You can supply your own, custom, hook scripts if you wish. Install gitolite as usual, then: * go to ~/.gitolite/hooks/common on the server and put your new hook there * now run "gl-setup" again You can use this procedure to install new hooks as well as to update hooks that you had previously installed. **IMPORTANT WARNINGS** * The `update` hook in `hooks/common` is what implements all the branch-level permissions in gitolite. If you fiddle with the hooks directory, please make sure you do not mess with this file accidentally, or all your fancy per-branch permissions will stop working. * Do not under any conditions put anything in `hooks/gitolite-admin` -- nothing in gitolite requires you to do anything here. Leave it alone! ### #hookchaining hook chaining Sometimes you need to use git hooks for your own purposes (site-local validations, CI integration, email notifications, or the ever popular "live website update"!). However, the hooks you want to use may already be in use by gitolite. This section will tell you what to do in such cases. First, let's list the hooks that gitolite uses: * The `update` hook is used in all repos and is critical to gitolite's access control! * The `post-receive` hook is used in all repos but only if mirroring has been enabled. Shipped as `post-receive.mirrorpush`, it is renamed to 'post-receive' and installed as part of the mirroring setup. * The `post-update` hook is used in the `gitolite-admin` repo only, to "compile" the configuration and so on. To run your own 'update' hook, just put it in a file called `update.secondary` and install it as a hook. Gitolite's update hook will automatically chain to it, taking care to pass it the same 3 arguments the original update hook received from git. > In addition, gitolite now contains the basic infrastructure to support > multiple 'update' hooks without having to remember to chain them yourself. > See `hooks/common/update.secondary.sample` for instructions. For `post-receive`, (if using mirroring) do the opposite. You're normally expected to rename the shipped 'post-receive.mirrorpush' to 'post-receive', but don't do this. Instead, simply run `hooks/post-receive.mirrorpush` at the end of *your* hook code. Do not worry about replicating STDIN (the documented way in which a post-receive hook receives its input) because the mirroring code does not use it. To run your own `post-update` hook on normal repos, just install a hook called 'post-update' the usual way. It'll be installed on all normal repos but not on the special gitolite-admin repo. If you need that for the gitolite-admin repo, you'll have to call it `post-update.secondary`. Finally, these names ('update.secondary' and 'post-update.secondary') are merely the defaults. You can change them to anything you want; look in conf/example.gitolite.rc for details. ### environment variables available to hooks The following environment variables are set, and may be useful for any custom processing you wish to do in your hook code: * `GL_USER` -- the user doing the push * `GL_REPO` -- the reponame * `GL_REPO_BASE_ABS` -- the absolute base path where all the repos are kept The following variables are also set, but are generally less useful: * `GL_BINDIR` -- where all the binaries live * `GL_ADMINDIR` -- common directory for many gitolite things ### "gl-post-init" hook Sometimes it is necessary to do something whenever a new repo is created. If you need this functionality, just supply a hook called "gl-post-init" with whatever code you want in it. ### #pre-git "gl-pre-git" hook Although git has lots of nice hooks you can tap into, they all run only on a push. There's nothing that runs on a fetch or a clone, and there's no way to run something *before* git-receive-pack or git-upload-pack, (as the case may be) are invoked. That's what the `gl-pre-git` hook is for. If an executable hook called `gl-pre-git` is present, it will be invoked with the current directory set to `repo.git`, and with a single argument which will be either `R` or `W` depending on what the client is trying to do. The environment variables `GL_USER` and `GL_REPO` are available. STDOUT will be forced to STDERR before it is called, to avoid confusing the client. If the code returns anything other than 0, gitolite will terminate the operation (i.e., not run git at all), just like many git hooks do, so make sure you end with `exit 0` or equivalent. ## other features ### F=moverepos moving pre-existing repos into gitolite It's best to split this into different use cases. **Case 1 -- few repos**: This is for moving one or two repos at a time, when you have a copy of the repo on your workstation. It is also the *only* way if you have push rights to the admin repo but no *shell* privileges on the server. * let gitolite create it as a brand new repo as described in the section on "adding users and repos" at the top * cd to the clone on your workstation. Make sure all the branches are correct and no extra stuff, "temp" branches, etc., are present * now run these two commands git push --all git@server:reponame git push --tags git@server:reponame * (You could also use "git push --mirror" instead of separately doing branches and tags, but that will carry across *your* remote refs also, and typically you may not want that. Anyway please do a `git ls-remote git@server:repo` to make sure all the stuff you want went through, and is named correctly). **Case 2 -- many repos**: This is when you have many existing repos to add, and they're all bare (as good little server repos should be) and you have shell access on the server. Here's how to do it; please note the order is important here: * make doubly sure they're *bare* repos ;-) * log on to the server and copy the repos to `$REPO_BASE` (which defaults to `~/repositories`), making sure that the directory names end in ".git". * back on your workstation, add each repo (without the `.git` suffix) to `conf/gitolite.conf` in your gitolite-admin repo clone. Give *some* user (even a non-existent one like "DUMMY" is fine) at least "R" access to these repos. Then add, commit, push. **Case 3 -- far too many repos** (or your initials are JH ;-): This is when you're like Case 2, except you have *so many* repos that step 3 becomes too cumbersome (even with a script doing it for you). Assuming you can group your repo names into various patterns, and can use similar access control lines within each such group, you can use gitolite's "wildcard repos" feature. First read the [wildcard repositories][wild] document, or at least skim through it, to understand the basic concept. Then do this: * do step 1 just like step 1 in Case 2 above * ditto for step 2 * for each repo, determine who the owner should be and create files called `gl-creater` (note spelling!) in each repo. The file should contain exactly one line with the owner name. * run `gl-setup` again (you don't need to supply a pub key filename) * finally add the repos to the conf, maybe something like this, (in this example, the owner name was the second component of the repo path), and add/commit/push: repo pub/CREATOR/..* C = @developers RW+ = CREATOR RW = WRITERS R = READERS **Details** * why is the order of steps different in case 1 and case 2? Because in case 2, the actual data is coming from an OS 'cp' (copy) command, not via a normal push like in case 1. Since that happens outside gitolite, it's easier to do it first, then tell gitolite about the repo so it can add hooks. (If you tell gitolite first, it will create an empty repo as soon as you push, then your 'cp' will have to overwrite those files, but you'll then lose gitolite's hooks, etc. A bit more messy). * what's with the `gl-creater` file in case 3? What the [wildcard repositories][wild] document does not explain is how ownership is *recorded* in gitolite: the `gl-creater` file contains the owner name. If you want to "pretend" these repos were created by some user, you need to add that in. That user then gets whatever access you gave to "CREATOR" in the access rules (in our example, that was `RW+`). * why does case 3 need the `gl-setup` command? An admin push only checks hooks on normal (non-wildcard) repos. It would be too timetaking otherwise. Running `gl-setup` forces it to do this more aggressively than an admin push, looking at wildcard repos as well as normal ones. In the end, it all boils down to (a) making sure the `update` hook is correct on all repos, wild or normal, and (b) making sure `gl-creater` contains the owner name for wild repos. The rest of the setup is in the conf file. ### F=moveserver moving the whole thing from one server to another [**NOTE**: I would appreciate help testing these instructions] Just copying everything won't work unless everything on the new server is exactly the same. I suggest you don't try it unless you know what you're doing. **Assumptions** * you have not changed `$REPO_BASE` on either of the servers; if you did, substitute accordingly * the admin's name is "YourName" -- again, substitute accordingly! * the "hosting user" on both servers is "git". Substitute whatever you're actually using (for example, if you're installing using RPM/DEB, this would be "gitolite") There are many ways of doing this, but the most *generic* set of steps are given below. Please follow all the steps; do not skip or improvise! Ask me if things are not clear -- you can help me fine tune this document :-) * (workstation, old server) **pull** the latest changes to the `gitolite-admin` repo to your workstation, if you don't have them already. You'll need them later on. * (old server) **disable** the old server so your users will not push any changes to it. There are several ways to do this, but the simplest is to insert this line at the top of `~/.gitolite.rc` on the old server: exit 1; * (new server) **copy** the repos to the new server, **except** the `gitolite-admin` repo and files called `gitolite-hooked` in the `hooks` directory of each repo. That sounds complicated but it's not. It's just: cd $HOME rsync -a olduser@oldhost:repositories . mv repositories/gitolite-admin.git $HOME/old-gitolite-admin.git find repositories -name gitolite-hooked | xargs rm Don't forget to chown repositories if git's UID changed. Gitolite expects the hosting user to own all the files and directories it manages. * (workstation, new server) **install** gitolite normally on your new server. Use whatever install method suits you, but you must use the **same** name for the admin ("YourName" in the install instructions). You may use a different keypair if you need to, or use the same one that currently gets access to the old server. * (new server) **edit** the `~/.gitolite.rc` file to match the settings on the old server, if needed. Do not copy the entire file outright -- some of the variables (notably `GL_PACKAGE_CONF` and `GL_PACKAGE_HOOKS`) are installation dependent and should not be touched! Do a diff or a vimdiff and copy across only what you know *you* changed on the old server. * (workstation) **push** the config to the new server. To do this, go to your admin clone, and: * if you used a different keypair when installing to the new server, copy that pubkey to this clone into `keydir/Yourname.pub`, then add and commit the change to the pubkey cd gitolite-admin cp path/to/new/YourName.pub keydir/YourName.pub git add keydir git commit -m "new server, new key" * if you did *not* use a different keypair, just make a dummy commit git commit -m "new server" --allow-empty * set the URL for the new server git remote set-url origin git@newserver:gitolite-admin * push the config, including past history git push -f And that should be that! ### custom git config The custom hooks feature is a blunt instrument -- all repos get the hook you specified and will run it. You can of course install hooks manually on the server, but sometimes that's cumbersome. Instead, you could set your hooks to only work if a certain "gitconfig" variable was set. See [this][rsgc] for a way to specify "git config" settings on a per repository basis. sitaramc-gitolite-6857652/doc/auth.mkd000066400000000000000000000102511171610012500175370ustar00rootroot00000000000000# F=auth authentication versus authorisation This document will explain why an "ssh issue" is almost never a "gitolite issue", and, indirectly, why I dont get too excited about the former. Note: for actual ssh troubleshooting see [this][sts]. Here is a fundamental point: **Gitolite does not do authentication. It only does authorisation**. So first, let's loosely define these words: > **Authentication** is the process of verifying that you are who you claim > to be. An authentication system will establish that I am the user > "sitaram" on my work system. The one behind gmail will similarly > establish that I am "sitaramc". And so on... > **Authorisation** is the process of asking what you want to do and > deciding if you're allowed to do it or not. Now, if you managed to read about [gitolite and ssh][gl_ssh], you know that gitolite is meant to be invoked as: /full/path/to/gl-auth-command some-authenticated-gitolite-username (where the "gitolite username" is a "virtual" username; it does not have to be, and usually *isn't*, an actual *unix* username). As you can see, authentication happens before gitolite is called. ## but... but... you have all that ssh stuff in there! The default mode of using gitolite does use ssh keys, but all it's doing is helping you **setup** ssh-based authentication **as a convenience to you**. You don't have to use it, though. And many people don't. The examples I know are [smart http][http], and ldap-backed sshd. In both cases, gitolite has no role to play in creating users, setting up their passwords/keys, etc. There's even a `GL_NO_SETUP_AUTHKEYS` option to make sure gitolite doesn't meddle with the authkeys file in such installations. ## so you're basically saying you won't support "X" (where "X" is some ssh related behaviour change or feature) Well, if it's not a security issue I *probably* won't. I'm willing to change my mind if enough people convince me they need it. (There's a mailing list if you want to find others who also need the same thing.) While we're on the subject, locking someone out is *not* a security issue. Even if you locked yourself (the admin) out, the docs tell you how to recover from such errors. You do need some password based method to get a shell command line on the server, of course. ## appendix: how to use other authentication systems with gitolite The bottom line in terms of how to invoke gitolite has been described above, and as long as you manage to do that gitolite won't even know how the authentication was done. Which in turn means you can use whatever authentication scheme you want. It also expects the `SSH_ORIGINAL_COMMAND` environment variable to contain the full command (typically starting with git-receive-pack or git-upload-pack) that the client sent. Also, when using [smart http][http], things are somewhat different: gitolite uses certain environment variables that it expects httpd to have set up. Even the user name comes from the `REMOTE_USER` environment variable instead of as a command line argument in this case. However, it has to be an authentication system that is compatible with sshd or httpd in some form. Why? Because the git *client* accessing the server only knows those 2 protocols to "speak git". (Well, the `git://` protocol is unauthenticated, and `file://` doesn't really apply to this discussion, so we're ignoring those). For example, let's say you have an LDAP-based authentication system somewhere. It is possible to make apache use that to authenticate users, so when a user accesses a git url using `http://sitaram:password@git.example.com/repo`, it is LDAP that does the actual authentication. [I wouldn't know how to do it but I know it is possible. Patches to this doc explaining how are welcome!] There are also ssh daemons that use LDAP to store the authorised keys (instead of putting them all in `~/.ssh/authorized_keys`). The clients will still need to generate keypairs and send them to the admin, but they can be more centrally stored and perhaps used by other programs or tools simultaneously, which can be useful. Finally, gitolite allows you to store *group* information externally too. See [here][ldap] for more on this. sitaramc-gitolite-6857652/doc/big-config.mkd000066400000000000000000000273001171610012500206050ustar00rootroot00000000000000# F=bc what is a "big-config" This document is just background info; you don't actually need to read the whole thing if you don't care. All you need to do is set `BIG_CONFIG` to 1 in the rc file and you're done. If you have no use for gitweb, git-daemon, or [git config][rsgc], you can save even more time by setting `GL_NO_DAEMON_NO_GITWEB`. Finally, if you're *really* an expert (or your initials are "JK"), you can even set `GL_NO_CREATE_REPOS` and `GL_NO_SETUP_AUTHKEYS`. However, be warned that if you're not sufficiently clueful, those last 2 variables could have a [security impact][rcsecurity]. ## when/why do we need it? A "big config" is anything that has a few thousand users and a few thousand repos, resulting in a very large 'compiled' config file. ### the problem To understand the problem, consider what happens if you have something like this in your gitolite conf file: @wbr = lynx firefox @devs = alice bob repo @wbr RW+ next = @devs RW master = @devs Without the 'big config' setting, gitolite internally translates this to: repo lynx firefox RW+ next = alice bob RW master = alice bob and then generates the actual config rules once for each user-repo-ref combination (there are 8 combinations above); the compiled config file looks somewhat like [this][bigno_]. Of course, the output is the same whether you used groups (like `@wbr` and `@devs` in the example above) or listed the repos directly on the 'repo' lines. Anyway, you can imagine what that does when you have 10,000 users and 10,000 repos. Let's just say it's not pretty :) ## how do we use it? Just set $GL_BIG_CONFIG = 1; in the `~/.gitolite.rc` file on the server (see next section for more variables). When you do that, and push this configuration, one of two things happens. ### access rules for groups If you used group names in the 'repo' lines (as in `repo @wbr`), then the compiled config looks like [this][bigyes_]. That's a lot smaller, and allows orders of magintude more repos and groups to be supported. ### access rules for individual repos (split config) If, on the other hand, you had the repos listed individually, (as in `repo lynx firefox`), then the main config file would now look like this: %repos = (); %split_conf = ( 'firefox' => 1, 'lynx' => 1 ); And each individual repo's configuration would go its own directory. For instance, `~/repositories/lynx.git/gl-conf` would look like this: %one_repo = ( 'lynx' => { 'R' => { 'alice' => 1, 'bob' => 1 }, 'W' => { 'alice' => 1, 'bob' => 1 }, 'alice' => [ [ 0, 'refs/heads/next', 'RW+' ], [ 4, 'refs/heads/master', 'RW' ] ], 'bob' => [ [ 1, 'refs/heads/next', 'RW+' ], [ 5, 'refs/heads/master', 'RW' ] ] } ); That does not reduce the overall size of the repo config (because you did not group the repos), but the main repo config is now even smaller! ## what are the downsides? There are some downsides. The following apply if individual ("split") conf files are written, which in turn only happens if you used repo names instead of group names on the `repo` lines: * the compile (gitolite-admin push) is now slower, because it potentially has to write a few thousand small files instead of one large one. Since the compile should be relatively infrequent compared to developer access, this is ok -- the main config file is parsed much faster now, so every hit to the server will benefit. * we can no longer distinguish 'repo not found on disk' from 'you dont have access'. They both now look like 'you dont have access'. ## other optimisations ### disabling various defaults The default RC file contains the following lines (we've already discussed the first one): $GL_BIG_CONFIG = 0; $GL_NO_DAEMON_NO_GITWEB = 0; $GL_NO_CREATE_REPOS = 0; $GL_NO_SETUP_AUTHKEYS = 0; `GL_NO_DAEMON_NO_GITWEB` is a very useful optimisation that you *must* enable if you *do* have a large number of repositories, and do *not* use gitolite's support for gitweb or git-daemon access (see "[this][gwd]" for details). This will save a lot of time when you push the gitolite-admin repo with changes. This variable also controls whether "git config" lines (such as `config hooks.emailprefix = "[gitolite]"`) will be processed or not. You should be a lot more careful with `GL_NO_CREATE_REPOS` and `GL_NO_SETUP_AUTHKEYS`. These are meant for installations where some backend system already exists that does all the actual repo creation, (including setting up the proper hooks -- very important for access control), and all the authentication setup (ssh auth keys), respectively. Summary: Please **leave those two variables alone** unless you're initials are "JK" ;-) ### #authkeyopt optimising the authkeys file Sshd does a linear scan of the `~/.ssh/authorized_keys` file when an incoming connection shows up. This means that keys found near the top get served faster than keys near the bottom. On my laptop, it takes about 2500 keys before I notice the delay; on a typical server it could be double that, so don't worry about all this unless your user-count is in that range. One way to deal with 5000+ keys is to use customised, database-backed ssh daemons, but many people are uncomfortable with taking non-standard versions of such a critical piece of the security infrastructure. In addition, most distributions do not make it painless to use them. So what do you do? The following trick uses the Pareto principle (a.k.a the "80-20 rule") to get an immediate boost in response for the most frequent or prolific developers. It can allow you to ignore the problem until the next big increase in your user counts! Here's how: * create subdirectories of keydir/ called 0, 1, (maybe 2, 3, etc., also), and 9. * in 0/, put in the pubkeys of the most frequent users * in 1/, add the next most important set of users, and so on for 2, 3, etc. * finally, put all the rest in 9/ Make sure "9" contains at least 70-90% of the total number of pubkeys, otherwise this doesn't really help. You can easily determine who your top users are by runnning something like this (note the clever date command that always gets you last months log file!) cat .gitolite/logs/gitolite-`date +%Y-%m -d -30days`.log | cut -f2 | sort | uniq -c | sort -n -r ## F=ldap storing usergroup information outside gitolite (like in LDAP) [Please NOTE: this is all about *user* groups, not *repo* groups] [WARNING: the earlier method of doing this has been discontinued; please see the commit message for details] Gitolite now allows usergroup information to be stored outside its own config file. We'll see "why" first, then the "how". ### #ldapwhy_ why Large sites often have LDAP servers that already contain user and group information, including group membership details. Such sites may prefer that gitolite just pick up that info instead of having to redundantly put it in gitolite's config file. Consider this example config for one repo: repo foo RW+ = @lead_devs RW = @devs R = @interns Normally, you would also need to specify: @lead_devs = dilbert alice @devs = wally @interns = ashok However, if the corporate LDAP server already tags these people correctly, and if there is some way of getting that information out **at run time**, that would be cool. ### #ldaphow_ how All you need is a script that, given a username, queries your LDAP or similar server, and returns a space-separated list of all the groups she is a member of. If an invalid user name is sent in, or the user is valid but is not part of any groups, it should print nothing. This script will probably be specific to your site. (See contrib/ldap for some example scripts that were contributed by the Nokia MeeGo team.) Then set the `$GL_GET_MEMBERSHIPS_PGM` variable in the rc file to the full path of this program, set `$GL_BIG_CONFIG` to 1, and that will be that. ## implementation notes To understand how big-config works (at least when you're using grouped repos), we'll first look at how it works without this setting. Think back to the example at the top, and assume 'alice' is accessing the 'lynx' repo. The various rights are governed by the following hash elements: # for the first level checks $repos{'lynx'}{'R'}{'alice'} = 1 $repos{'lynx'}{'W'}{'alice'} = 1 # for the second level checks $repos{'lynx'}{'alice'}{'refs/heads/master'} = 'RW'; $repos{'lynx'}{'alice'}{'refs/heads/next'} = 'RW+'; Those elements are explicitly specified in the compiled hash, as you can see (you don't need to know perl too much to read a hash; just make some educated guesses if needed!) Now look at the compiled hash produced when `GL_BIG_CONFIG` is set. In place of both 'firefox' and 'lynx' you have '@wbr', and similarly '@devs' for both 'alice' and 'bob'. In addition, there is a group hash at the bottom that lists each group and its members. When 'alice' tries to access the 'lynx' repo, gitolite collects all the group names that these names belong to, so '@devs' is added to the list of 'user' names that 'alice' inherits permissions from, and '@wbr' is added to the list of 'repo' names that 'lynx' inherits from. This means that the final access inherits all permissions pertaining to the following combinations: alice, lynx alice, @wbr @devs, lynx @devs, @wbr (Actually there are 3 more... try and guess what they may be!) Anyway, all ACL rules for these combinations are clubbed together to make the composite set of rules that 'alice' accessing 'lynx' is subject to. ## config listings ### F=bigno_ compiled config with big-config disabled %repos = ( 'firefox' => { 'R' => { 'alice' => 1, 'bob' => 1 }, 'W' => { 'alice' => 1, 'bob' => 1 }, 'alice' => [ [ 0, 'refs/heads/next', 'RW+' ], [ 4, 'refs/heads/master', 'RW' ] ], 'bob' => [ [ 1, 'refs/heads/next', 'RW+' ], [ 5, 'refs/heads/master', 'RW' ] ] }, 'lynx' => { 'R' => { 'alice' => 1, 'bob' => 1 }, 'W' => { 'alice' => 1, 'bob' => 1 }, 'alice' => [ [ 2, 'refs/heads/next', 'RW+' ], [ 6, 'refs/heads/master', 'RW' ] ], 'bob' => [ [ 3, 'refs/heads/next', 'RW+' ], [ 7, 'refs/heads/master', 'RW' ] ] } ); Phew! ### F=bigyes_ compiled config with big-config enabled %repos = ( '@wbr' => { '@devs' => [ [ 0, 'refs/heads/next', 'RW+' ], [ 1, 'refs/heads/master', 'RW' ] ], 'R' => { '@devs' => 1 }, 'W' => { '@devs' => 1 } } ); %groups = ( '@devs' => { 'alice' => 'master', 'bob' => 'master' }, '@wbr' => { 'firefox' => 'master', 'lynx' => 'master' } ); sitaramc-gitolite-6857652/doc/delegation.mkd000066400000000000000000000133321171610012500207140ustar00rootroot00000000000000# F=deleg delegating access control responsibilities ---- ## lots of repos, lots of users Gitolite tries to make it easy to manage access to lots of users and repos, exploiting commonalities wherever possible. It lets you specify bits and pieces of the access control separately -- i.e., *all* the access specs for a certain repo need not be together; they can be scattered, which makes it easier to manage the sort of slice and dice needed in that example. But eventually the config file will become too big. If you let only one person have control, he could become a bottleneck. If you give it to multiple people, they might make mistakes or stomp on each others' work accidentally. The best way is to divide up the config file and give parts of it to different people. Ideally, we would delegate authority for *groups* of repos, not individual repos, otherwise it doesn't scale. It would also be nice if we could prevent an admin from creating access rules for *any* repo in the system -- i.e., set limits on what repos he can control. This would be a nice "security" feature. Delegation offers a way to do all that. You can allow access control rules for a set of repos to be specified in a **subconf** file and allow someone (a **sub-admin**) to make changes within that file. (Note: sub-admins cannot create or remove users). ## how to use delegation First, you group your repos however you want. In the example below, I'm considering firefox and lynx (projects at the root of the gitolite server) as well as *any* repo inside the `browsers` subdirectory, as members of the `webbrowsers` group. Similarly for the others. @webbrowsers = firefox lynx browsers/..* @webservers = apache nginx servers/..* @malwares = conficker storm ms/..* # side note: if anyone objects, we claim ms stands for "metasploit" ;-) Each of these groups is called a **subconf** from here on. Then you designate a **sub-admin** to manage each subconf, and you ensure (using the standard gitolite feature of restricting pushes by names of changed files) that a sub-admin can make changes only to her subconf file and nothing else. For example, Alice is in charge of all web browser development projects. Similarly, Bob takes care of web servers, and Mallory, as [tradition][abe] dictates, is in charge of malware ;-) [abe]: http://en.wikipedia.org/wiki/Alice_and_Bob#List_of_characters # the admin repo access was probably like this to start with: repo gitolite-admin RW+ = sitaram # now add these lines to the config for the admin repo RW = alice bob mallory RW+ NAME/ = sitaram RW NAME/conf/subs/webbrowsers = alice RW NAME/conf/subs/webservers = bob RW NAME/conf/subs/malwares = mallory Finally, you tell gitolite to pull in these files using the "subconf" command subconf "subs/*.conf" You can put this command anywhere in the main gitolite.conf file, but it's best to put it at the end. Now alice can clone the admin repo, add a file called `conf/subs/webbrowsers` with whatever access rules she wants for the repositories under her control, commit and push. And that's really all there is to it. ### #subconf the subconf command This command is much like the "include" command, but in addition it checks that a subconf does not contain ACL rules for repos that are outside its purview. In the above example, the `webbrowsers` subconf file can only have access control lines for firefox, lynx, and anything under "browsers/" because those are the elements of the `@webbrowsers` group. (This is checked using a regex match, which is why "anything under browsers/" is written `browsers/..*`) In more precise terms: * the subconf name is simply the basename of the conf file, without the .conf extension, and * the elements of an `@` group of the same name are then used to limit what repos the subconf can have ACL lines for. (Additional notes: it can also contain lines for an actual repo called `webbrowsers`, or, in big-config mode, for a group called `@webbrowsers`). ### backward compatibility For backward compatibility, if no `subconf` commands have been seen at the end of processing the main config file, gitolite pretends you appended subconf "conf/fragments/*.conf" to the end of the file. ## security notes ### group names You can use "@group"s defined in the main config file but do not attempt to redefine or extend them in your own subconf file. If you must extend a group (say `@foo`) defined in the main config file, do this: @myfoo = @foo # now do whatever you want with @myfoo Group names you define in your subconf will not clash even if the exact same name is used in another subconf file, so you need not worry about that. ### delegating pubkeys Short answer: not gonna happen. The delegation feature is meant only for access control rules, not pubkeys. Adding/removing pubkeys is a much more significant event than changing branch level permissions for people already on staff, and only the main admin should be allowed to do it. Gitolite's "userids" all live in the same namespace. This is unlikely to change, so please don't ask -- it gets real complicated to do otherwise. Allowing sub-admins to add users means username collisions, which also means security problems (admin-A creates a pubkey for Admin-B, thus gaining access to all of Admin-B's stuff). If you feel the need to delegate even that, please just go the whole hog and give them separate gitolite instances! It's pretty easy to setup the *software* itself system-wide, so that many users can use it; see the root install method in the install document. sitaramc-gitolite-6857652/doc/developer-notes.mkd000066400000000000000000000134171171610012500217200ustar00rootroot00000000000000# F=dev_notes developer/patch maintainer notes ## 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. ## 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`. ## modules There are 3 "modules" (`gitolite_rc`, `gitolite_env`, and `gitolite` itself). Their purposes should be fairly obvious. ## 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`. ### from perl * for frequently run perl programs, I prefer my method * gl-auth-command -- this is invoked with a full path * gl-mirror-shell -- same as above * 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) ### 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-setup * gl-tool * gl-mirror-push ### OUTLIER! * gl-admin-push 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! ## special types of setups ### 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. There are also some other "impedance mismatches" that may show up. For example, the gl-setup triggered by detecting a change in `$data_version` following an RPM update once caused problems. This auto-update is designed to run on the next "hit" of any kind (which arguably makes things very easy in normal installations), but in Fedora's case it also means it runs *as that user*. Who may not have "write" access to `$GL_ADMINDIR`! So the compile fails, and you now have new code trying to work with old format data. The solution is to explicitly run a compile, from a properly privileged userid, as soon as you do an RPM upgrade. # **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 sitaramc-gitolite-6857652/doc/gitolite-and-ssh.mkd000066400000000000000000000163361171610012500217630ustar00rootroot00000000000000# F=gl_ssh how gitolite uses ssh Although other forms of authentications exist (see the document on [authentication versus authorisation][auth]), ssh is the one that most git users use. ***Therefore, gitolite is (usually) heavily dependent on ssh***. Most people didn't realise this, and even if they did they don't know ssh well enough to help themselves. If you don't understand how ssh public key authentication works, or how the `~/.ssh/authorized_keys` file can be used to restrict users, etc., you will have endless amounts of trouble getting gitolite to work, because you'll be attacking the wrong problem. So please please please understand this before tearing your hair out and blaming ***git/gitolite*** for whatever is going wrong with your setup :-) ## ssh basics Let's start with some basics, focusing *only* on the pieces relevant to `gitolite`. If this is not detailed enough, please use google and learn more from somewhere, or maybe buy the OReilly ssh book. * You can login to an ssh server by typing a password, but ssh can also use ***public-private keys*** (also called "key pairs") for authentication. `gitolite` *requires* you to use this mechanism for your users -- they cannot log in using passwords. Hopefully by the time you finish reading this document you will understand why :-) The way you set this up is you generate a key pair on your workstation, and give the server the public key. (I need not add that the "private" key must be, well, kept *private*!) * **generating a key pair on your workstation** is done by running the command `ssh-keygen -t rsa`. This produces two files in `~/.ssh`. One is `id_rsa`; this is the **private** key -- ***never*** let it out of your machine. The other is `id_rsa.pub`, which is the corresponding public key. This public key is usually just one long line of text. * on Windows machines with msysgit installed, you should do this from within a "git bash" window. The command will report the full path where the files have been written; make a note of this, and use those files in any of the description that follows * **adding your public key to the server**'s `~/.ssh/authorized_keys` file is how ssh uses pubkeys to authenticate users. Let's say sita@work.station is trying to log in as git@serv.er. What you have to do is take the `~/.ssh/id_rsa.pub` file for user sita on work.station and append its contents (remember it's only one line) to `~/.ssh/authorized_keys` for user git on serv.er. The `authorized_keys` file can have multiple public keys (from many different people) added to it so any of them can log in to git@serv.er. In the normal case (not gitolite, but your normal everyday shell access), there's a command that does this, `ssh-copy-id`, which also fixes up permissions etc., as needed, since sshd is a little picky about allowing pubkey access if permissions on the server are loose. Or you can do it manually, as long as you know what you're doing and you're careful not to erase or overwrite the existing contents of `~/.ssh/authorized_keys` on the server! But in the gitolite case, it's different; we'll get to that in a minute. * **troubleshooting pubkey authentication failures**: if you are unable to get ssh access to the server after doing all this, you'll have to look in `/var/log/secure` or `/var/log/auth.log` or some such file on the server to see what specific error `sshd` is complaining about. * **restricting users to specific commands** is very important for gitolite. If you read `man sshd` and look for `authorized_keys file format`, you'll see a lot of options you can add to the public key line, which restrict the incoming user in various ways. In particular, note the `command=` option, which means "regardless of what the incoming user is asking to do, forcibly run this command instead". Also note that when there are many public keys (i.e., lines) in the `authorized_keys` file, each line can have a *different* set of options and `command=` values. Without this `command=` option, the ssh daemon will simply give you a shell, which is not what we want for our gitolite keys (although we may well have other keys which we use to get a shell). **This is the backbone of what makes gitolite work; please make sure you understand this**. ## how does gitolite use all this ssh magic? These are two different questions you ought to be having by now: * how does it distinguish between me and someone else, since we're all logging in as the same remote user "git" * how does it restrict what I can do within a repository ### restricting shell access/distinguishing one user from another The answer to the first question is the `command=` we talked about before. If you look in the `authorized_keys` file, you'll see entries like this (I chopped off the ends of course; they're pretty long lines): command="[path]/gl-auth-command sitaram",[more options] ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA18S2t... command="[path]/gl-auth-command usertwo",[more options] ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEArXtCT... First, it finds out which of the public keys in this file match the incoming login. That's crypto stuff, and I won't go into it. Once the match has been found, it will run the command given on that line; e.g., if I logged in, it would run `[path]/gl-auth-command sitaram`. So the first thing to note is that such users do not get "shell access", which is good! Before running the command, however, sshd sets up an environment variable called `SSH_ORIGINAL_COMMAND` which contains the actual git command that your workstation sent out. This is the command that *would have run* if you did not have the `command=` part in the authorised keys file. When `gl-auth-command` gets control, it looks at the first argument ("sitaram", "usertwo", etc) to determine who you are. It then looks at the `SSH_ORIGINAL_COMMAND` variable to find out which repository you want to access, and whether you're reading or writing. Now that it has a user, repository, and access requested (read/write), gitolite looks at its config file, and either allows or rejects the request. But this cannot differentiate between different branches within a repo; that has to be done separately. ### restricting branch level actions [If you look inside the git source tree, there's a file among the "howto"s in there called `update-hook-example.txt`, which was the inspiration for this part of gitolite.] Git allows you to specify many "hooks", which get control as various events happen -- see `git help hooks` for details. One of those hooks is the `update` hook, which, if it is present, is invoked just before a branch or a tag is about to be updated. The hook is passed the name of the branch or tag, the old SHA1 value, and the new SHA1 value, as arguments. Hooks that are called *before* an action happens are allowed to prevent that action from happening by returning an error code. When gitolite is told to create a new repository (by the admin), it installs a special update hook. This hook takes all the information presented, looks at the config file, and decides to allow or reject the update. And that's basically it. sitaramc-gitolite-6857652/doc/gitolite-gitweb-http-backend.mkd000066400000000000000000000204501171610012500242410ustar00rootroot00000000000000# F=ggshb how to set up gitolite+gitweb+ssh+http-backend ## NAME gitolite-gitweb-http-backend ## DESCRIPTION You've been tasked with rolling out gitolite and git-web in your corporate environment and your requirements are as follows: 1. git access must be via both ssh and http[s] 2. browsable via git-web 3. your web server must run as a user different from that of the git user 4. The repository has its own virtual host Note that these instructions are geared toward OpenSuSE 11.4. Feel free to modify the examples below to your environment. ## EXAMPLE ENVIRONMENT The following assumptions are made for the purposes of example: * The server name will be git.example.com * Repositories are located in `/srv/git` and are owned by the `git` user * Apache 2.2.\* running as `wwwrun:www` will be used as the web server * gitolite has been installed via package management (yum, zypper, apt-get, etc) * gitweb browsing is via http://git.example.com/ * The repositories can be cloned from the following URLs: * git@git.example.com:<repo-name> * http://git.example.com/<repo-name>.git * HTTP authentication is handled via a local htpasswd file * http://git.example.com will be a virtual host * Two git repositories will be created: * engineering * operations ## GITOLITE SETUP Install gitolite via your package management tools. Under OpenSuSE, this will install repositories in `/srv/git`. Follow the instructions found [here][install] for initial set up. ### gitolite.rc You will need to tell gitolite.rc about some additional keys that will be needed for each repository. Make sure the following config option is set in `/srv/git/.gitolite.rc`: $GL_GITCONFIG_KEYS = "gitweb.url receive.denyNonFastforwards receive.denyDeletes"; These options tell gitolite to allow the user to set these values in `gitolite.conf`, which in turn will be propagated to each repositories git config. ### gitolite.conf For the purposes of example, we assume that we have two groups accessing each repository: engineering and operations. So, our `gitolite.conf` file will look something like this: # # Group Definitions # @engineering = daniel erik alex jose mark @operations = james chris long bora dmitriy @gladmin = james chris # # Repository Definitions # # Note that we give access to the daemon user, thus enabling # git-daemon-export-ok (see # https://github.com/sitaramc/gitolite/blob/pu/doc/2-admin.mkd#gwd) repo gitolite-admin RW = @sysops daemon R = @all repo engineering RW = @engineering @gladmin daemon R = @all config gitweb.url = git@git.example.com:engineering config receive.denyNonFastforwards = true config receive.denyDeletes = true repo operations RW = @operations @engineering @gladmin daemon R = @all config gitweb.url = git@git.example.com:operations config receive.denyNonFastforwards = true config receive.denyDeletes = true repo @all R = daemon gitweb # additional configuration ... Save, commit, and push your changes to the gitolite-admin repo as described [here][conf]. ## APACHE SETUP Under OpenSuSE 11.4, Apache runs as user `wwwrun` group `www` (see `/etc/apache2/uid.conf`). But wait! How can Apache running as `wwwrun` commit to git repositories, which are owned by `git`? ### suexec Enter SuExec. This is an apache module that allows apache to run under the auspicious of a different user. For this to work, we need to do some setup ahead of time. First, we need to make sure the `suexec` program has the right permissions: # OpenSuSE 11.4 puts the suexec program under /usr/sbin/suexec2 $ chgrp www /usr/sbin/suexec2 $ chmod 4750 /usr/sbin/suexec2 # Verify permissions $ ls -al /usr/sbin/suexec2 -rwsr-x--- 1 root www 14944 Feb 18 20:53 /usr/sbin/suexec2 Next, we need to create a wrapper script for the suexec program and place that under the correct directory. To find out the where to place the wrapper script, do the following: $ /usr/sbin/suexec2 -V -D AP_DOC_ROOT="/srv/www" -D AP_GID_MIN=96 -D AP_HTTPD_USER="wwwrun" -D AP_LOG_EXEC="/var/log/apache2/suexec.log" -D AP_SAFE_PATH="/usr/local/bin:/usr/bin:/bin" -D AP_UID_MIN=96 -D AP_USERDIR_SUFFIX="public_html" The variable we are interested in is `AP_DOC_ROOT` which is `/srv/www`. So, we place the wrapper script in `/srv/www/bin/gitolite-suexec-wrapper.sh` with the following contents: #!/bin/bash # # Wrapper for gl-auth-command # USER=$1 export GIT_PROJECT_ROOT="/srv/git/projects" export GITOLITE_HTTP_HOME="/srv/git" # OpenSuSE gitolite RPM places gl-auth-command in /usr/bin exec /usr/bin/gl-auth-command $USER # End _For security purposes, this file MUST exist under `/srv/www`!_ Finally, make sure Apache loads the suexec module. Under OpenSuSE, this would mean adding "suexec" to `APACHE_MODULES` in `/etc/sysconfig/apache2`. ### Gitweb As gitweb will now be run under the `git` user, all files must be under `/srv/www` as well. # Under OpenSuSe, git-web installs in /usr/share/gitweb $ cp -r /usr/share/gitweb /srv/www $ chown -R git.git /srv/www/gitweb Do not forget to point `$projectroot` in `/etc/gitweb.conf` to `/srv/git/projects`! ### Virtual Host Configure your virtual host as follows: ServerName git.example.com ServerAlias git # By default, use gitweb DocumentRoot /srv/www/gitweb # Suexec setup SuexecUserGroup git git # Set up appropriate GIT environments SetEnv GIT_PROJECT_ROOT /srv/git/projects SetEnv GIT_HTTP_EXPORT_ALL # Set up appropriate gitolite environment SetEnv GITOLITE_HTTP_HOME /srv/git # To serve gitweb at the same url, use a ScriptAliasMatch to # only those URLs that git http-backend can handle, and # forward the rest to gitweb: ScriptAliasMatch \ "(?x)^/(.*/(HEAD | \ info/refs | \ objects/(info/[^/]+ | \ [0-9a-f]{2}/[0-9a-f]{38} | \ pack/pack-[0-9a-f]{40}\.(pack|idx)) | \ git-(upload|receive)-pack))$" \ /srv/www/bin/gitolite-suexec-wrapper.sh/$1 # Make sure we can execute gitweb okay Options ExecCGI AllowOverride None AddHandler cgi-script .cgi DirectoryIndex gitweb.cgi Order allow,deny Allow from all # We need gl-auth-command executable Order allow,deny Allow from all # Set up authentication to taste AuthType Basic AuthName "Private Git Access" Require valid-user AuthUserFile /srv/git/passfile ## VALIDATION Once apache has been restarted, verify your configuration: - Repository browsable via gitweb - Check out repository via ssh - Check out repository via http - Commit over ssh git@git.example.com - Commit over http ## ADDITIONAL RESOURCES - [http://httpd.apache.org/docs/2.2/suexec.html](http://httpd.apache.org/docs/2.2/suexec.html) Apache suexec documentation - [http://www.kernel.org/pub/software/scm/git/docs/git-http-backend.html](http://www.kernel.org/pub/software/scm/git/docs/git-http-backend.html) git-http-backend(1) documentation - [https://git.wiki.kernel.org/index.php/Gitweb](https://git.wiki.kernel.org/index.php/Gitweb) git-web documentaiton - [http://sitaramc.github.com/gitolite/doc/http-backend.html](http://sitaramc.github.com/gitolite/doc/http-backend.html) gitolite http backend documentation ## AUTHOR Christopher M. Fuhrman << cfuhrman at panix dot com >> sitaramc-gitolite-6857652/doc/gitolite.conf-by-example.mkd000066400000000000000000000203241171610012500234050ustar00rootroot00000000000000# F=conf_examples gitolite.conf -- by example I hate people who make statements like "I dont have time to learn". People with that sort of attitude shouldn't use gitolite at all, and I refuse to spoon-feed them or be their personal tutor. However, it's possible that even with the right attitude and willingness to learn, some people just get a mental block about something, and so I figure this might help. (Side note: followup questions not welcome from people in the former category; you know who you are). **WARNING 1**: in case of conflict between this document and reality, reality wins. For conflict between this document and the [main document][conf], the main document wins. In any case, please bring such issues to my notice. **WARNING 2**: this document has examples only for the most commonly used features. If you don't find a feature here, look in the main document before asking me. **WARNING 3**: Read the WHOLE document. I can't keep saying, for instance, that "rewind" actually means any of 3 different things so I'll say it only once. It's upto you to have read that part also. ## general notes Git branch/tag name **recap**: branches look like refs/heads/something, tags look like refs/tags/something. When there is no ambiguity, we leave out the refs/heads/ and the refs/tags/. A "rewind" means any of 3 things: force-push a branch (make it go backward, using 'git push -f' or equivalent), delete a branch, or update a tag. The first two are clearly information-losing operations so it is wise to require special rights to do them. The third is in the same category because tags are supposed to be "write once" so **re**-writing a tag is considered abnormal. These examples are only for the more complex parts of the conf file. We're not going to discuss things like what characters are allowed in a username or reponame, how to write a comment line, how to write continuation lines (you can't), include files, and all such *lexical* issues. ## F=regexov extremely brief regex overview Regexes are powerful. Gitolite uses that power as much as it can. If you can't handle that power, hire someone who can and become a manager. That said, here's a very quick overview of the highlights. `^` and `$` are called "anchors". They anchor the match to the beginning and end of the string respectively. ^foo matches any string starting with 'foo' foo$ matches any string ending with 'foo' ^foo$ matches exact string 'foo'. To be precise, the last one is "any string starting and ending with *the same* 'foo'". "foofoo" does not match. `[0-9]` is an example of a character class; it matches any single digit. `[a-z]` matches any lower case alpha, and `[0-9a-f]` is the range of hex characters. You should now guess what `[a-zA-Z0-9_]` does. `.` (the period) is special -- it matches any character. If you want to match an actual period, you need to say `\.`. `*`, `?`, and `+` are quantifiers. They apply to the previous token. `a*` means "zero or more 'a' characters". Similarly `a+` means "one or more", and `a?` means "zero or one". As a result, `.*` means "any number (including zero) of any character". The previous token need not be a single character; you can use parens to make it longer. `(foo)+` matches one or more "foo", (like "foo", "foofoo", "foofoofoo", etc.) ## F=exbac basic access control repo gitolite-admin RW+ = sitaram # this is equivalent to: RW+ refs/.* = sitaram Sitaram is the only admin. He can push, create, delete, or rewind any branch or tag in the gitolite-admin repo. repo testing RW+ = @all The 'testing' repo is a play area for everyone. Anyone can do anything to any branch or tag on it. repo foo RW+ = sitaram dilbert RW = alice ashok R = wally Wally can only read the repo. Alice and Ashok can push but not rewind; only Sitaram and Dilbert can do that. And now, a common misunderstanding: R master = wally # WILL NOT DO WHAT YOU THINK IT DOES!! This won't work. Please see [here][rpr_] for more on this. repo foo RW master$ = dilbert alice # this is equivalent to: RW refs/heads/master$ = dilbert alice The reason for treating "master$" as "refs/heads/master$" is that matching branches is the most common use so the syntax is optimised to make that simpler to write and easier to read. Anything *not* starting with `refs/` (or `NAME/`, but that is out of scope for this document), is implicitly prefixed with `refs/heads/`. The `master$` is called a "refex" (a regex that matches a ref). Dilbert and Alice can push to the "master" branch. Unless some other rule allows it, they cannot push to, say, "master1", "masterfull" etc., due to the `$` at the end of the refex. Refexes are *prefix matched*; i.e., treated as if they have a `^` at the start. (This means `^refs/heads/master` in this case, not `^master`, in case you forgot!) This rule therefore does not match "headmaster", or even "refs/heads/refs/heads/master" (yes, it is possible to confuse yourself by pushing a branch like that in git). RW+ pu = dilbert # again, remember this is equivalent to: RW+ refs/heads/pu = dilbert Dilbert can push any branch whose name starts with "pu". This includes "pu1", "pupu", "pu/up", and so on, not just "pu". This is because there is no `$` at the end. RW junk/ = wally Wally can push any branch under "junk/", which means "junk/foo", "junk/bar", are ok but not "junk1" or even "junk". RW tmp/ = @all Similar to above, but for *any* authenticated user. RW refs/tags/v[0-9] = ashok # the QA guy Ashok is allowed to push version tags. He can push any tag whose name starts with a "v", then a digit, like "v1", "v1.0", "v2.0rc1", etc., but not "v-1", "ver1". ## F=exaac advanced access control ### "deny" rules **Warning**: When using deny rules, the order of your rules matters, where earlier it did not. PROCESSING LOGIC: > The first matching refex that has the permission you're looking for (`W` > or `+`) **or a minus (`-`)**, results in success **or failure, > respectively**. A fallthrough **also** results in failure. RW refs/tags/v[0-9] = ashok - refs/tags/v[0-9] = @staff RW refs/tags = @staff This allows only Ashok to write "version tags" (see rule for Ashok the QA guy somewhere above). The others can write any tags they want, *except* version tags. To understand this, try and match each rule *in sequence* with the name of the tag being pushed, and the user doing it, applying the logic described earlier. * for non-version tags, only the 3rd rule matches, so anyone on staff can push them * for version tags by ashok, the first rule matches so he can push them * for version tags by staffers *other than ashok*, the second rule matches before the third one, and it has a `-` as the permission, so the push fails ### #ruleaccum2 rule accumulation Rules accumulate. Even when separated by rules for other repos. They accumulate intuitively. For example: repo foo RW+ = alice repo bar RW+ = dilbert repo @all RW dev/USER/ = @staff repo foo RW+ tmp/ = @staff has the **effective** ruleset, for repo foo, of repo foo RW+ = alice RW dev/USER/ = @staff RW+ tmp/ = @staff Just remember that if you use [deny rules][deny] anywhere then the *order of the rules matters*! ### gitweb and daemon Gitolite does NOT do anything for gitweb and daemon access **except** * for daemon, create the file `git-daemon-export-ok` in the repository * for gitweb, add the repo (plus owner name, if given) to the list of projects to be served by gitweb (see the config file variable `$PROJECTS_LIST`, which should have the same value you specified for `$projects_list` when setting up gitweb) * put the description, if given, in `$repo/description` sitaramc-gitolite-6857652/doc/gitolite.conf.mkd000066400000000000000000000633621171610012500213550ustar00rootroot00000000000000# F=conf the access control file `gitolite.conf` Gitolite has an advanced access control language that is designed to be powerful but easy to use. Other objectives were that it should be even easier to read, review and audit the rules, and it should scale to thousands of repos and users. There was also, in the author's mind, a desperate need to create something as different as possible from the brain-dead, nausea-inducing "Windows INI" style syntax that some other popular tools seem to favour. ## #confrecap a quick summary of the conf file This is a quick *recap* of the *most common* elements in a typical config file. (In the following description, the angle brackets are not part of the syntax). * (optional) [group][groups] definitions @ = * repo access rules repo # one or more access rules like = The [most common][bac] permissions are: * R, for read only * RW, for push existing ref or create new ref * RW+, for "push -f" or ref deletion allowed (i.e., destroy information) * `-` (the minus sign), to [deny][] access If no [refex][] is supplied, the access rule applies to all refs. Later, gitolite [acquired][rwcd] * RWC and RW+C, for ref creation. Once you use this in a repo, a normal RW/RW+ can no longer create a ref; it can only push existing refs (Note also that a standalone "C" permission is something [completely different][wild]) * RWD and RW+D, for ref deletion. Once you use this in a repo, a normal RW+ can no longer delete a ref; it can only rewind * (RWCD and RW+CD, which is just a combination of these two) Even later, [gitolite acquired][mergecheck], if you want to enforce a rebase-only workflow. The following sections dig deeper into the syntax and semantics of the access control rules and other configuration directives in the `gitolite.conf` file. If narrative descriptions scare you, or your English is not upto scratch, try [gitolite config by example][conf_examples] also. ## F=syntax lexical syntax In general, everything is **space separated**; there are no commas, semicolons, etc., in the syntax. **Comments** are in the usual shell-ish style. **User names** and **repo names** are as simple as possible; they must start with an alphanumeric, but after that they can also contain `.`, `_`, or `-`. Usernames can optionally be followed by an `@` and a domainname containing at least one `.` (this allows you to use an email address as someone's username). Reponames can contain `/` characters (this allows you to put your repos in a tree-structure for convenience) ### continuation lines There are no continuation lines -- gitolite does not process C-style backslash-escaped newlines as anything special. However, the section on "groups" will tell you how you can break up large lists of names in a group definition into multiple lines. ### include files Gitolite allows you to break up the configuration into multiple files and include them in the main file for convenience. include "foo.conf" will include the contents of the file "foo.conf". Details: * You can also use a glob (`include "*.conf"`), or put your include files into subdirectories of "conf" (`include "foo/bar.conf"`), or both (`include "repos/*.conf"`). * Included files are always searched from the gitolite-admin repo's "conf/" directory, unless you supplied an absolute path. (Note: in the interests of cloning the admin-repo sanely you should avoid absolute paths!) * If you ended up recursing, files that have been already processed once are skipped, with a warning. Advanced users: `subconf`, a command that is very closely related to `include`, is documented [here][subconf]. ## F=bac basic access control Here's a very basic set of rules: repo gitolite-admin RW+ = sitaram repo testing RW+ = @all repo gitolite simplicity RW+ = sitaram dilbert RW = alice ashok R = wally It should be easy to guess what most of this means: * `R` means "read" permission * `RW` means "read and write", but no rewind * `RW+` means "read and write", with rewind allowed A "rewind" is more often called a "non-fast forward push"; see git docs for what that is. The `+` was chosen because it is part of the "git push" syntax for non-ff pushes. Note that *tags* are generally considered "write once", so they are treated specially by gitolite: you need rewind permission (`RW+`) to *overwrite* a tag with a new value. In a later section you'll see some more advanced permissions. ### how rules are matched It's important to understand that there're two levels at which access control happens. Please see [this][2levels] for details, especially about the first level check. Much of the complexity applies only to the second level check, so that is all we will be discussing here. This check is done by the update hook, and determines whether the push succeeds or fails. For basic permissions like this, matching is simple. Gitolite already knows: * the user * the repo * the branch or tag ("ref") being pushed * whether it is a normal (ff) push or a rewind (non-ff) push. Gitolite goes down the list of rules matching the user, repo, and the ref. The first matching rule that has the permission you're looking for (`W` or `+`), results in success. A fallthrough results in failure. ### #refex branches, tags, and specifying "refex"es One of the original goals of gitolite was to allow access control at the branch/tag (aka "ref") level. The git source code contains a sample update hook that has the following in it: # from Documentation/howto/update-hook-example.txt: refs/heads/master junio +refs/heads/pu junio refs/heads/cogito$ pasky refs/heads/bw/.* linus refs/heads/tmp/.* .* refs/tags/v[0-9].* junio If you did this in gitolite, this is what the equivalents would be: repo git RW master$ = junio # line 1 RW+ pu$ = junio # line 2 RW cogito$ = pasky # line 3 RW bw/ = linus # line 4 RW tmp/ = @all # line 5 RW refs/tags/v[0-9] = junio # line 6 The following points will help you understand these rules. (Git recap: branches and tags together are called "ref"s in git. A branch ref usually looks like "refs/heads/foo", while a tag ref looks like "refs/tags/bar") * the general syntax of a paragraph of rules is: # start line: repo [one or more repos and/or repo groups] # followed by one or more permissions lines: [permission] [zero or more refexes] = [one or more users] * a **refex** is a *perl regex* that matches a ref. When you try to push a commit to a branch or a tag, that "ref" is matched against the refex part of each rule. * if the refex does not start with `refs/`, gitolite assumes a prefix of `refs/heads/`. This is useful because *branch* matching is the most common case, as you can see this applies to lines 1 through 5 here. * if no refex appears, the rule applies to all refs in that repo (as if you had specified `refs/.*` as the refex). * refexes are prefix-matched (they are internally anchored with `^` before being used). This means only the beginning of the actual ref needs to match the refex, unless the refex has an explicit `$` meta-character at the end (like the first 3 lines in our example do). Line 5, for instance, allows anyone to push a branch inside the "tmp/" namespace, while line 6 provides the ability to push version tags; "v1", "v1.0", "v2.0rc1", all match the criterion specified by `v[0-9]` because this is a prefix match only. ### #groups groups Gitolite allows you to define **groups** of repos. users, or even refexes. A group is semantically (but *not* syntactically) like a `#define` in C. Here is an example of each kind: @oss_repos = gitolite linux git perl rakudo entrans vkc @staff = sitaram some_dev another-dev @important = master$ QA_done refs/tags/v[0-9] The syntax of a group definition is simply: @groupname = [one or more names] A group can *accumulate* values. For example: @staff = sitaram some_dev another-dev @staff = au.thor is the same as @staff = sitaram some_dev another-dev au.thor This is more convenient than continuation lines, because it allows you to add to a group anywhere. Many people generate their gitolite.conf itself from some *other* database, and it is very useful to be able to do this sort of thing. Groups can include other groups, and the included group will be expanded to whatever value it *currently* has: @staff = sitaram some_dev another-dev # line 1 @staff = au.thor # line 2 @interns = indy james # line 3 @alldevs = bob @interns @staff # line 4 "@alldevs" expands to 7 names now. However, remember that the config file is parsed in a single-pass, so later *additions* to a group name cannot affect earlier *uses* of it. If you moved line 2 to the end, "@alldevs" would only have 6 names in it. #### the special `@all` group There's a special group called `@all` that includes all authenticated users when used as a username; you've seen examples of it earlier. Advanced users: also see the entry for `GL_ALL_INCLUDES_SPECIAL` in the documentation for [`~/.gitolite.rc`][rc]. When used as a reponame, it includes all repos physically present in `~/repositories` (or whatever is the value of `$REPO_BASE`). ### F=rpr_ side note: "R" permissions for refs You can control "read" access only at the repo level, not at the branch level. For example, this **won't** limit Wally to reading only the master branch: repo foo R master = wally # WILL NOT DO WHAT YOU THINK IT DOES!! and this **won't** prevent him from reading it: repo foo - master = wally # WILL NOT DO WHAT YOU THINK IT DOES!! R = wally This (inability to distinguish one ref from another during a read operation) is a git issue, not a gitolite issue. There are 3 ways around this, though: * switch to gerrit, which has its own git stack, its own sshd, and God knows what else. All written in Java, the COBOL of the internet era ;-) * bug the git people to add this feature in ;-) * use a separate repo for Wally. Using separate repos is not that hard with gitolite. Here's how to maintain a [partial copy][partialcopy] of the main repo and keep it synced (while not allowing the secret branches into it). ## F=aac advanced access control The previous section is sufficient for most common needs, but gitolite can go a lot further than that. ### restricting pushes by dir/file name using NAME/ Here's a hopefully self-explanatory example. Assume the project has the following contents at the top level: a README, a "doc/" directory, and an "src/" directory. repo foo RW+ = lead_dev # rule 1 RW = dev1 dev2 dev3 dev4 # rule 2 RW NAME/ = lead_dev # rule 3 RW NAME/doc/ = dev1 dev2 # rule 4 RW NAME/src/ = dev1 dev2 dev3 dev4 # rule 5 Notes: * the "NAME/" is part of the syntax; think of it as a keyword if you like. The rest of it is treated as a refex to match against each file being touched (see "SPECIFYING AND USING A REFEX" above for details) * file/dir NAME-based restrictions are *in addition* to normal (branch-name based) restrictions; they are not a *replacement* for them. This is why rule #2 (or something like it, maybe with a more specific branch-name) is needed; without it, dev1/2/3/4 cannot push any branches. * if a repo has *any* NAME/ rules, then NAME-based restrictions are checked for *all* users. This is why rule 3 is needed, even though we don't actually have any NAME-based restrictions on lead_dev. Notice the pattern on rule 3. * *each* file touched by the commits being pushed is checked against those rules. So, lead_dev can push changes to any files, dev1/2 can push changes to files in "doc/" and "src/" (but not the top level README), and dev3/4 can only push changes to files in "src/". ### #deny "deny" rules #### warnings and required reading Gitolite performs access checks at 2 levels. The first check is performed for both read *and* write operations, while the second one happens only for write operations. **Required reading**: [this section][2levels] of the documentation. **Warning**: When using deny rules, the order of your rules matters, where earlier it did not. If you're just starting to add a deny rule to an existing ruleset, it's a good idea to review the entire ruleset once, to make sure you're doing it right. #### "deny" rules for refs in a repo You can use "deny" rules for the second check, to prevent people pushing branches or tags that they should not be allowed to. Take a look at the following snippet, which *seems* to say that "bruce" can write versioned tags (anything containing `refs/tags/v[0-9]`), but the other staffers can't: @staff = bruce whitfield martin [... and later ...] RW refs/tags/v[0-9] = bruce RW refs/tags = @staff But that's not how the matching works. As long as any refex matches the refname being updated, it's a "yes". Since the second refex (which says "anything containing `refs/tags`") is a superset of the first one, it lets anyone on `@staff` create versioned tags, not just Bruce. So how do we say "these people can create any tags except tags that look like this pattern"? One way to fix this is to allow "deny" rules. We make a small addition to the permissions syntax, and define a more rigorous, ordered, interpretation. Let's recap the **existing semantics**: > The first matching refex that has the permission you're looking for (`W` > or `+`), results in success. A fallthrough results in failure. Here are the **new semantics**, with changes from the "main" one in bold: > The first matching refex that has the permission you're looking for (`W` > or `+`) **or a minus (`-`)**, results in success **or failure, > respectively**. A fallthrough **also** results in failure. So the example we started with becomes, if you use "deny" rules: RW refs/tags/v[0-9] = bruce - refs/tags/v[0-9] = @staff RW refs/tags = @staff And here's how it works: * for non-version tags, only the 3rd rule matches, so anyone on staff can push them * for version tags by bruce, the first rule matches so he can push them * for version tags by staffers *other than bruce*, the second rule matches before the third one, and it has a `-` as the permission, so the push fails #### "deny" rules for the entire repo The previous section described deny rules for the second check, which is a fairly common need. However, gitolite does not process deny rules for the first check -- it's usually simple enough to make sure your config file does not allow the particular user/repo combindation at all. But there's one case where this becomes cumbersome: when you want to create exceptions to uses of `@all`. For example, if you want gitweb to show `@all` repos except the special 'gitolite-admin' repo, you must manually (and laboriously) maintain a list of all your repos (except gitolite-admin) and use that in place of `@all`. Oh joy... But now you can do this: repo gitolite-admin - = gitweb daemon [... other access rules ...] config gitolite-options.deny-repo = 1 repo @all R = gitweb daemon Here are some notes on how/why this works: * The order is important -- the deny rule must come first. * The 'config' line is what tells git to behave differently, i.e., apply deny rules for the first check also. * Since there is no "ref" to match against the refexes in the rules, gitolite just ignores the refexes, and simply looks at the permission (R, RW, "-", etc) and the user list. ### #rwcd creating and deleting branches Since the beginning of gitolite, `RW` gave the ability, not only to update, but to *create* a branch (that matched the refex). Similarly, `RW+` meant being able to not only rewind, but also delete a ref. Conceptually, a rewind is almost the same as a delete+push (the only difference I can see is if you had core.logAllRefUpdates set, which is *not* a default setting). However, there seem to be cases where it is useful to distinguish these cases. Arguments can be made on all sides if you're dealing with new users, so gitolite now supports that (in a backward compatible way). We'll look at the delete/rewind case in detail first: * if the rules for a repo do not contain a `D` anywhere, then `RW+` will allow both rewind and delete operations. Apart from being more convenient if you don't need this separation, this also ensures backward compatibility for setups created before this separation feature was added to gitolite). * if, however, *any* of the rules for a repo contains a `D` (example: `RWD`, `RW+D`, etc) then `RW+` by itself will permit only a rewind, not a delete The same thing applies to create/push, where if you have permissions like `RWC` or `RW+C` anywhere in that repo, a simple `RW` or `RW+` can no longer *create* a new ref. You can combine the `C` and `D` also. Thus, the set of permissions you now know about are, in regex syntax: `R|RW+?C?D?`. See a later section for the full set of permissions possible. Some usage hints: * if you find that `RW+` no longer allows creation/deletion but you can't see a `C`/`D` permission in the rules, remember that gitolite allows a repo config to be specified in multiple places for convenience, included delegated or included files. Be sure to search everywhere :) * a quick way to make this the default for *all* your repos is: repo @all RWCD dummy-branch = foo where foo can be either the administrator, or if you can ignore the warning message when you push, a non-existant user. ### #mergecheck enforcing a no-merges policy Some people want to enforce a no-merges policy for various reasons. This behaviour can be enabled by suffixing an "M" to the end of any permission starting with `RW` (i.e., all of them except `R`). So for instance, `RW` becomes `RWM`, and `RW+` becomes `RW+M`, etc. The rules are exactly the same as for "C" and "D": once a repo has an "M" qualifier tied to any access rule, all rules for that repo are subject to merge checking, and merge commits will only be allowed when the rule has the "M" qualifier. ## summary: permissions The full set of permissions, in regex syntax: `-|R|RW+?C?D?M?`. This expands to one of `-`, `R`, `RW`, `RW+`, `RWC`, `RW+C`, `RWD`, `RW+D`, `RWCD`, or `RW+CD`, all but the first one optionally followed by an `M`. And by now you know what they all mean. [Side note: There is one more very important permission to be dealt with -- the standalone `C`, which is not really a "ref" level permission and can be found in doc/wildcard-repositories.mkd.] ## F=confother_ other tips ### personal branches Gitolite lets you define a "personal" or "scratch" namespace prefix for each developer. See [here][pers] for details. ### #ruleaccum rule accumulation (Also see [this][ruleaccum2] for a different example that may be more intuitive for some people). Gitolite lets you specify access rules for a repo in bits and pieces, and accumulates them in the same sequence they were given. This is very convenient. Let's say you have a mix of open source and closed source projects, and "bosses" should have read access to all projects, and everyone should have read access to open source projects. Assuming the appropriate group definitions, this would work: # all bosses have read access to all projects repo @open @closed @topsecret R = @bosses # everyone has read access to "open" projects repo @open R = @bosses @devs @interns If you notice that `@bosses` are given read access to `@open` via both rules, don't worry that this causes some duplication or inefficiency. It doesn't :-) Elsewhere in the file, you would specify access for individual repos (like RW, RW+, etc). Gitolite combines all of these access rules, maintaining the textual order in which they occur, when authorising a push. And although this example used groups, you can use reponames as well, or mix and match them. You can even distribute rulesets across multiple "include" files if you wish. Just remember that if you use [deny rules][deny] anywhere then the *order of the rules matters*! This feature also helps people who generate their gitolite.conf itself from some *other* database -- it allows them much more flexibility in how they generate rules. ### #gwd specifying gitweb and daemon access Gitolite allows you to specify access for git-daemon and gitweb. This is a feature that I personally do not use (corporate environments don't like unauthenticated access of any kind to any repo!), but someone wanted it, so here goes. Gitolite has two pre-defined, "special", usernames: `daemon` and `gitweb`. To make a repo or repo group accessible via "git daemon", just give read permission to the special user "daemon". Similarly, give read permission to `gitweb` to allow the gitweb CGI to show the repo. Something like this: repo foo bar baz R = gitweb daemon This gives you a quick way to offer multiple repos up for gitweb and/or daemon access. However, **setting a description** for the project also enables gitweb permissions so you can do it that way if you want. Of course in this case you have to deal with each repo separately. Add lines like this to gitolite.conf: foo = "some description" bar = "some other description" baz = "yet another description" You can also **specify an owner** for gitweb to show, if you like; for example I might use: gitolite "Sitaram Chamarty" = "fast, secure, fine-grained, access control for git" These lines are standalone, so you can add them anywhere in the conf file. Note that gitolite does **not** install or configure gitweb/git-daemon -- that is a one-time setup you must do separately. All gitolite does is: * for daemon, create the file `git-daemon-export-ok` in the repository * for gitweb, add the repo (plus owner name, if given) to the list of projects to be served by gitweb (see the config file variable `$PROJECTS_LIST`, which should have the same value you specified for `$projects_list` when setting up gitweb) * put the description, if given, in `$repo/description` The "compile" script will keep these files consistent with the config settings -- this includes removing such settings/files if you remove "read" permissions for the special usernames or remove the description line. Please **note** that giving permissions to these special users via `@all` (that is, using either `repo @all` or `R = @all`), will not work unless you set the rc-file variable `$GL_ALL_INCLUDES_SPECIAL` to `1`. Also, **NOTE** that giving them read access to `repo @all` means the `gitolite-admin` repo is also accessible. **It is upto you to decide if that is OK in your environment**. ### #rsgc repo specific `git config` commands (Thanks to teemu dot matilainen at iki dot fi) > ---- > **Note**: this won't work unless the rc file has the right settings; > please see `$GL_GITCONFIG_KEYS` in the [rc file doc][rc] for details and > security information. > ---- Sometimes you want to specify `git config` settings for your repos. For example, say you have a custom post-receive hook that sends an email when a push happens, and this hook looks in the config for whom to send the email to, etc. You can set these git config values within a "repo" paragraph: repo gitolite config hooks.mailinglist = gitolite-commits@example.tld config hooks.emailprefix = "[gitolite] " config foo.bar = "" config foo.baz = The syntax is simple: config sectionname.keyname = [optional value_string] This does either a plain "git config section.key value" (for the first 3 examples above) or "git config --unset-all section.key" (for the last example). Other forms of the `git config` command (`--add`, the `value_regex`, etc) are not supported. > ---- > **WARNING**: simply deleting the config line from the `conf/gitolite.conf` > file will *not* delete the variable from `repo.git/config`. The syntax in > the last example is the *only* way to make gitolite execute a > `--unset-all` operation on the given key. > ---- You can repeat the 'config' line as many times as you like, and the last occurrence will be the one in effect. This allows you to override settings just for one project, as in this example: repo @all config gitolite.mirror.master = "frodo" config gitolite.mirror.slaves = "sam gollum" repo top-secret-project # only sam, because we don't trust gollum config gitolite.mirror.slaves = "sam" The "delete config variable" syntax can also be used, if you wish: repo highlander # there can be only one! config gitolite.mirror.master = config gitolite.mirror.slaves = As you can see, the general idea is to place the most generic ones (`repo @all`, or repo patterns like `repo foo.*`) first, and place more specific ones later to override the generic settings. sitaramc-gitolite-6857652/doc/gitolite.rc.mkd000066400000000000000000000356561171610012500210410ustar00rootroot00000000000000# F=rc configuring gitolite's advanced features -- the `.gitolite.rc` file This is the documentation for the contents of the "rc" file (`$HOME/.gitolite.rc`) on the server. Until now this documentation was inline, within the rc file itself, but it has grown too large, too unwieldy, and too difficult to grok for people new to gitolite. The documentation follows approximately the same order as the sample variables in the (now reorganised) example "rc" file. [Note: in perl, there is no actual boolean. The undefined value, the number '0', and the empty string, are all 'false'. Everything else is 'true'. It is thus common to use just 0/1 for false/true]. ## variables that should not be touched at all The first section does not need too much elaboration. Let's just say bad things happen if you change them. ## most often used/changed variables * `$GL_WILDREPOS`, boolean, default 0 Setting this variable lets your users create repositories based on wild cards, hand out R and RW permissions to other users to collaborate, etc. See [this][wild] for lots of info on this. * `$PROJECTS_LIST`, filename, default `~/projects.list` This is for gitweb users only. Gitweb setup has a variable called `$projects_list` (please see gitweb docs for more on this). Set this to the same value as that one. * `$GITWEB_URI_ESCAPE`, boolean, default undef Apparently gitweb expects project names to be URI-escaped (but seems to work fine even if you don't). If you need strict compatibility with gitweb, add/uncomment this variable and set it to 1. * `$REPO_UMASK`, octal, default `0077` The default UMASK that gitolite uses makes all the repos and their contents have `rwx------` permissions. People who want to run gitweb realise that this will not do. The correct way to deal with this is to give this variable a value like `0027` (note the syntax: the leading 0 is required), and then make the user running the webserver (apache, www-data, whatever) a member of the 'git' group. If you've already installed gitolite then existing files will have to be fixed up manually (for a umask or 0027, that would be `chmod -R g+rX`). This is because umask only affects permissions on newly created files, not existing ones. ## variables with an efficiency/performance impact * `$GL_BIG_CONFIG`, boolean, default 0 This is the most common setting for efficiency in handling large repo/user groups. This is a very powerful setting; please read [this][bc] if you need details. There are 3 other settings related to big configs. They are changed only in rare cases, however, so are described later. * `$GL_NO_DAEMON_NO_GITWEB`, boolean, default 0 If you have *lots* of repos, and you're *not* using gitweb or daemon, you should probably set this on for efficiency. Despite the name, it also blocks repo config settings. Please read [this][bc] for more details. **WARNING**: if your description files are maintained by some other means than via the gitolite config file, make sure you set this variable to 1. * `$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... * `$BIG_INFO_CAP`, number, default 20 See [using patterns to limit output][limitoutput] for details. ## #rcsecurity variables with a security impact **IMPORTANT NOTE** This section describes variables that, if not carefully used, can cause security issues. It also includes variables which I personally do not use and do not have the ability to test thoroughly Using non-default value for these variables voids the security reward in the README. This does *not* mean they are less important or that I will ignore problems; it just means *my* ability to catch problems may be limited by my test suite, my actual production use, my time, and sometimes (LDAP comes to mind) even my skill or resources available to me, and that therefore I depend on feedback from my users to find or fix issues. * `$GL_ALL_READ_ALL`, boolean, default undef Eliminates the access control check for read access. Makes things much (**much**!) faster when you have 10,000 projects and the compiled conf file is more than 20MB in size! **Double check with your boss or have a new job lined up before setting this on!** * `$GIT_PATH`, string, default empty If git on your server is on a standard path (that is `ssh git@server git --version` works), leave this setting as is. Otherwise, find out where it is and use that value here, for example `GIT_PATH="/opt/bin/";` * `$GL_GITCONFIG_KEYS`, string, default empty This setting allows the repo admin to define acceptable gitconfig keys. Gitolite allows you to set git repo options using the "config" keyword; see the section on "repo specific git config commands" in the [gitolite.conf][conf] documentation for details and syntax. However, if you are in an installation where the repo admin does not (and should not) have shell access to the server, then allowing him to set arbitrary repo config options *may* be a security risk -- some config settings allow executing arbitrary commands! You have 3 choices. By default `$GL_GITCONFIG_KEYS` is left empty, which completely disables this feature (meaning you cannot set git configs via the repo config). The second choice is to give it a space separated list of settings you consider safe. (These are actually treated as a set of perl regular expression patterns, and any one of them must match). For example: `$GL_GITCONFIG_KEYS = "core\\.logAllRefUpdates core\\..*compression";` allows repo admins to set one of those 3 config keys (yes, that second pattern matches two settings from "man git-config", if you look). The third choice (which you may have guessed already if you're familiar with regular expressions) is to allow anything and everything: `$GL_GITCONFIG_KEYS = ".*";` NOTE that due to some quoting and interpolation issues I have not been able to look at, a literal "." needs to be specified in this string as `\\.` (two backslashes and a dot). So this is how you'd allow any keys in the "foo" section: `$GL_GITCONFIG_KEYS = "foo\\..*";` * `$GL_NO_CREATE_REPOS`, boolean, default 0 DO NOT CHANGE THIS unless you have other means to create repos and correctly populate them with the required hooks. No hooks, no access control; you have been warned! * `$GL_NO_SETUP_AUTHKEYS`, boolean, default 0 DO NOT CHANGE THIS unless you have other means to setup the authkeys file (`~/.ssh/authorized_keys`). In an extreme case, if you switch this on without also fixing up the authkeys file, users who you think you deleted may still have access. All in all, please be careful, as with any change that affects ssh. * `$GL_WILDREPOS_DEFPERMS`, string, default undef This sets default wildcard permissions for newly created wildcard repos. If set, this value will be used as the default user-level permission rule of new wildcard repositories. The user can change this value with the setperms command as desired after repository creation; it is only a default. Example: `$GL_WILDREPOS_DEFPERMS = 'R @all';` * `$HTPASSWD_FILE`, string, default empty Gitolite can help users run the htpasswd command in a secure manner (since gitolite has already identified them by an ssh key). If you want to enable this, give the variable the absolute path to whatever file apache (etc) expect to find the passwords in. Look in the docs for [linking gitweb authorisation with gitolite][gitwebauth] for more details on using this feature. * `$RSYNC_BASE`, string, default empty Gitolite can be used to allow fine grained control of the rsync command. This setting enables the rsync external command helper, by specifying the base path of all the files that are accessible via rsync. It must be an absolute path, like `$RSYNC_BASE = "/home/git/up-down";`. Leave it undefined or set to the empty string to disable the rsync helper. When enabled, it runs rsync with specific arguments, all presumably filled in correctly by the client-side rsync. However, I am not an expert on how rsync may be abused, so if it breaks, you get to keep both pieces! * `$SVNSERVE`, string, default empty Gitolite can also be used to gate access (though not at a fine grained level) to SVN if needed, passing authentication information on to `svnserve`. This setting allows launching svnserve when requested by the ssh client. This allows using the same SSH setup for both SVN and git access. Leave it undefined or set to the empty string to disable svnserve access. The setting will look something like (where the %u is substituted with the username): $SVNSERVE = "/usr/bin/svnserve -r /var/svn/ -t --tunnel-user=%u"; * hook chaining * `$UPDATE_CHAINS_TO`, string, default "hooks/update.secondary" * `$ADMIN_POST_UPDATE_CHAINS_TO`, string, default "hooks/post-update.secondary" By default, the update hook in every repo chains to "update.secondary". Similarly, the post-update hook in the admin repo chains to "post-update.secondary". If you're fine with the defaults, there's no need to do anything here. However, if you want to use different names or paths, change these variables. * `$GL_ADC_PATH`, string, default undef This setting enables admin defined commands. **WARNING**: Use this feature only if (a) you really know what you're doing and (b) you really, **really**, know what you're doing! Please read the [admin defined commands][ADCs] document for details. This is an extremely powerful and flexible feature, and naturally anything that flexible can be a security risk! * `$GL_GET_MEMBERSHIPS_PGM`, string, default undef Some sites would like to store group membership outside gitolite, because they already have it in (usually) their LDAP server, and it doesn't make sense to be forced to duplicate this information. Set the following variable to the name of a script that, given a username as argument, will return a list of groups that she is a member of. See the [big config][bc] doc for more details. Example: `$GL_GET_MEMBERSHIPS_PGM = "/usr/local/bin/expand-ldap-user-to-groups"` * `$GL_HTTP_ANON_USER`, string, default undef Analogous to running mob branches over ssh (as described in the [mob branches][mob]), this variable -- combined with appropriate setup described in [doc/http-backend.mkd][http] -- lets you pretend to gitolite that unauthenticated HTTP users are actually authenticated as this user. * `$GL_REF_OR_FILENAME_PATT`, string Set of allowed characters in refnames (and, if you have `NAME/` rules, in filenames as well). The default pattern is almost the same as `$REPONAME_PATT` with some additions. Although the current code is not at risk in any way even if we let in names containing strings like `$(command)`, and although I intend to make sure things stay that way, it's probably a good idea to trap weird filenames early. Just to be safe. You ought to be able to loosen the pattern by adding other characters to it, if you really need to. If you do, at least avoid backquotes and the dollar sign! ## less used/changed variables * `$GL_ALL_INCLUDES_SPECIAL`, boolean, default undef Giving access to @all users (as in `R = @all`) in the config normally does *not* include the special users "gitweb" and "daemon". If you want @all to include these two users, set this variable. * `$GL_WILDREPOS_PERM_CATS`, string, default "READERS WRITERS" Originally, we only allowed "R" and "RW" in the setperms command. Now we allow the admin to define other roles as she wishes (example: MANAGERS, TESTERS, etc). This variable is a space-separated list of the allowed roles. Please read the **[warning][rolenamewarn]** in the [wild][] document before using this feature. This is a VERY powerful feature and if you're not careful you could mess up the ACLs nicely. This is the internal default if you don't set it (like if you didn't update your ~/.gitolite.rc with new variables when you upgraded gitolite): $GL_WILDREPOS_PERM_CATS = "READERS WRITERS"; You can use your own roles in addition to the standard ones; I suggest you include READERS and WRITERS for backward compatbility though: $GL_WILDREPOS_PERM_CATS = "READERS WRITERS MANAGERS TESTERS"; ## rarely changed variables * `$GL_LOGT`, string, default `$GL_ADMINDIR/logs/gitolite-%y-%m.log` This is the template for location of the log files and format of their names. The default produces files like `~/.gitolite/logs/gitolite-2009-09.log`. If you make up your own templates, **PLEASE MAKE SURE** the directory exists and is writable; gitolite won't do that for you unless it is the default, ("$GL_ADMINDIR/logs") * `$GL_PERFLOGT`, string, default undef This gives the location of the performance log files. Uncomment and set this variable if you want performance logging. Performance log files are kept separate from access log files because they store different, usually much shorter term, information. * `$GL_SITE_INFO`, string, default undef Some installations would like to give their users customised information (like a link to their own websites, for example) so that users have a quick way to find some links or information. If this variable is defined, the "info" command will print it at the end of the listing. * `$REPO_BASE`, string, default "repositories" This is where all the repos go. If it's not an absolute path, it is considered to be relative to $HOME. Moving all the repositories after the install has completed is doable: just [disable writes][disable] to gitolite, move `~/repositories/*`, change this variable, then re-enable writes. ## constants that aren't! The source file `src/gitolite_rc.pm` defines a few "constants", for example: $R_COMMANDS=qr/^(git[ -]upload-pack|git[ -]upload-archive)$/; Let's say you want to disallow the archive feature, you would need to change this constant. As of this version, you can now change these "constants" also, simply by defining a new value for any or all of them in your `~/.gitolite.rc` file. If you use this to relax some of the patterns involved (for example, the value of `ADC_CMD_ARGS_PATT`), please be sure you know what you're doing. [dwr]: http://sitaramc.github.com/gitolite/doc/3-faq-tips-etc.html#_disabling_write_access_to_take_backups [limit]: http://sitaramc.github.com/gitolite/doc/report-output.html#_using_patterns_to_limit_output [gitconf]: http://sitaramc.github.com/gitolite/doc/gitolite.conf.html#_repo_specific_git_config_commands sitaramc-gitolite-6857652/doc/hook-propagation.mkd000066400000000000000000000117241171610012500220650ustar00rootroot00000000000000# F=hook_prop hook propagation in gitolite Some users like to know how hooks propagate, and when, and why there appear to be two places to put them, and so on. I'll try and set out the logic here. **Note**: You don't need to read all this just to add your own hooks or your own functionality to a hook that gitolite uses. See [here][customhooks] and [here][hookchaining]. ## what hooks do repos get? * `update`: all repos have this; it does the write-level access control (per-branch permissions). * `post-receive`: if you've enabled [mirroring][], all repos have this. * `post-update`: only the special "gitolite-admin" repo has this; it acts upon the changes you push to the admin repo. * `gitolite-hooked`: not really a hook, but a special, zero-byte file; see below. * you can add any other hooks you want (see note at the top of this doc), and gitolite will propagate them into all repos along with the others. ## when do repos "get" hooks? * When the "compile" script runs, any repo that is named explicitly in the conf file, and the "gitolite-hooked" file is not present, gets hooks. * When someone runs `gl-setup` on the server, then *all* repos physically present inside `$REPO_BASE` get them. A repo does not have to be defined in the config for this to happen. Also, this includes the initial install; if you already had some repos in `REPO_BASE` they get the hooks. * If `GL_WILDREPOS` is set, a repo gets hooks when a user creates a repo or uses the "fork" command in "contrib/adc". In the latter case the hooks are explicitly copied from the source repo using the "cp" command, not using the code internal to gitolite. ## what exactly does "get hooks" mean? The "hooks/" directory of the bare repo on the server will *forcibly* have symlinks created for each of the hooks mentioned in the "what hooks..." section. Other files are left alone, so you *can* manually add a hook file to specific repos, directly on the server, so long as there is no name clash. ## where do the symlinks point? There are two places the symlinks can point. * `$GL_PACKAGE_HOOKS/common` contains the "system" hooks. `GL_PACKAGE_HOOKS` is defined in the rc file. * `$HOME/.gitolite/hooks/common` has the "user" hooks. The special "post-update" hook for the equally special "gitolite-admin" repo is not in either of those places. It's in `../gitolite-admin` relative to them. Just don't worry about it, and don't fiddle with it. **There is no spoon**. `GL_PACKAGE_HOOKS` is "/var/gitolite/hooks" or some such path for RPM/DEB or "root" method, and "$HOME/share/gitolite/hooks" for the non-root method. (Basically, it's whatever you gave as the 3rd argument to 'gl-system-install' when you used the root or non-root methods, or whatever the packager decided if you used the RPM/DEB method). ## so where do I put my hooks? Put them in the "user" location (`~/.gitolite/hooks/common`). The "system" location hooks override the ones in the "user" location, as you can see from the picture below. This can be useful to enforce site-wide hooks when using the RPM/DEB or root install methods. (For the non-root install it's useless, since both locations are under the control of the user). .gv edge [dir=forward color="blue"] splines = false glph [ shape = none label = <
package/system hooks
$GL_PACKAGE_HOOKS/common
update yourhook1 yourhook2
>] gladh [ shape = none label = <
user hooks
~/.gitolite/hooks/common
update yourhook1 yourhook3
>] rh [ shape = none label = <
individual repo hooks
$REPO_BASE/reponame.git/hooks
update yourhook1 yourhook2 yourhook3
>] rh:y3:n .. gladh:y3 rh:u:n .. glph:u:s rh:y1:n .. glph:y1 rh:y2:n .. glph:y2 As you can see, when both locations have the same hook, the symlink points to the "system" hook. By default, the only reason you need to touch the "system" location is if you want to modify the 'update' hook, but why would you fiddle with the most important part of gitolite, huh? You're a good admin, and will use [hook chaining][hookchaining] properly, right? sitaramc-gitolite-6857652/doc/http-backend.mkd000066400000000000000000000167241171610012500211550ustar00rootroot00000000000000# F=http how to setup gitolite to use smart http mode **Note**: "smart http" refers to the feature that came with git 1.6.6, late 2009 or so. The base documentation for this is `man git-http-backend`. Do **NOT** read `Documentation/howto/setup-git-server-over-http.txt` and think that is the same or even relevant -- that is from 2006 and is quite different (and arguably obsolete). ## WARNINGS, plus stuff I need help with * I have NOT converted the test suite to use this mode. Volunteers to convert it to http access are welcome :-) * I have no idea how to handle the password issue other than creating a `~/.netrc` file and making it `chmod 600`. Anyway, http based access is inherently less secure than pubkeys so not much point worrying about it. * I have not tested any of the ancillary standalone programs (like gl-admin-push) in this mode. They're most likely going to crash and burn because `$HOME` is not defined or in the wrong place; manually set `HOME=$GITOLITE_HTTP_HOME` and hope for the best. Luckily most of them have to do with sshkeys so this may not matter. YMMV. * tested on stock Fedora 14; if you test on other environments please let me know how it worked out and if we need to adjust this document * tested https with dummy certs and `GIT_SSL_NO_VERIFY`; no reason why it shouldn't work on a proper setup with everything in place * have not tried making repos available to both ssh *and* http mode clients; (I'd guess it ought to work fine if the "apache" user was made login-able and given a proper $HOME and `~/.ssh/authorized_keys` and all that). If anyone has the energy to try that please let me know how that went. ## additional requirements * requires `GIT_PROJECT_ROOT` (see "man git-http-backend" for what this is) set explicitly (i.e., it is no longer optional). Please set it to some place outside apache's `DOCUMENT_ROOT`. ## detailed instructions I assume you've installed apache 2.x and git on the server. I assume your httpd runs under the "apache" userid; adjust instructions below if it does not. Similarly for "/var/www" and other file names/locations. I assume you have read the "[please read this first][insttrouble]" section of the main install document to get an idea of the general concepts and terminology (just ignore anything that is specific to ssh). ### install gitolite under "apache" Follow the "non-root" method, but since you can't even "su - apache", make the following variations when doing this as root: * `cd ~apache` first; this is `/var/www` on Fedora 14 * do this in the shell mkdir gitolite-home export GITOLITE_HTTP_HOME GITOLITE_HTTP_HOME=/var/www/gitolite-home PATH=$PATH:$GITOLITE_HTTP_HOME/bin * now run the following commands. These are really the first 3 steps of the "non-root" install (clone, mkdir, and gl-system-install), **except** you substitute `GITOLITE_HTTP_HOME` in place of `HOME`. Note that you do NOT run the gl-setup step yet. cd gitolite-home git clone /tmp/gitolite.git gitolite-source # or wherever your local clone is, or directly from git://github.com/sitaramc/gitolite cd gitolite-source GHH=$GITOLITE_HTTP_HOME # just for convenience in next 2 commands mkdir -p $GHH/bin $GHH/share/gitolite/conf $GHH/share/gitolite/hooks src/gl-system-install $GHH/bin $GHH/share/gitolite/conf $GHH/share/gitolite/hooks * after the gl-system-install step, add these to the **top** of /var/www/gitolite-home/share/gitolite/conf/example.gitolite.rc $ENV{GIT_HTTP_BACKEND} = "/usr/libexec/git-core/git-http-backend"; # or wherever you have that file; note: NO trailing slash $ENV{PATH} .= ":$ENV{GITOLITE_HTTP_HOME}/bin"; # note the ".=" here, not "=" * run gl-setup with the name of your admin user gl-setup sitaram * IMPORTANT: fix up ownerships chown -R apache.apache $GITOLITE_HTTP_HOME ### setup apache You will need to setup certain values in the httpd conf, as given in `man git-http-backend`. You can put all them into, for instance, `/etc/httpd/conf.d/gitolite.conf` and apache [at least on Fedora 14] will pick it up. These are the values to use; note that these are somewhat different from those in the manpage cited above, plus we have one extra variable: SetEnv GIT_PROJECT_ROOT /var/www/gitolite-home/repositories SetEnv GIT_HTTP_EXPORT_ALL # please see notes below on ssh+http access ScriptAlias /git/ /var/www/gitolite-home/bin/gl-auth-command/ # note trailing slash SetEnv GITOLITE_HTTP_HOME /var/www/gitolite-home AuthType Basic AuthName "Private Git Access" Require valid-user AuthUserFile /path/to/some/passwdfile Now create/update the password file in `/path/to/some/passwdfile` using the `htpasswd` command, and you're all done for the setup! ## usage Git URLs look like `http://user:password@server/git/reponame.git`. The custom commands, like "info", "expand" should be handled as follows. The command name will come just after the `/git/`, followed by a `?`, followed by the arguments, with `+` representing a space. Here are some examples: # ssh git@server info curl http://user:password@server/git/info # ssh git@server info repopatt curl http://user:password@server/git/info?repopatt # ssh git@server info repopatt user1 user2 curl http://user:password@server/git/info?repopatt+user1+user2 It gets even more interesting for the `setperms` command, which expects STDIN. I didn't want to get too much into the code here, so I found that the following works and I'm leaving it at that: (echo R user1 user2; echo RW user3 user4) | curl --data-binary @- http://user:password@server/git/setperms?reponame.git With a few nice shell aliases, you won't even notice the horrible convolutions here ;-) ## allowing anonymous access Like [mob branches][mob] with ssh, you can allow completely **un**-authenticated users to still have some rights specified in gitolite. Briefly, here's how: * specify a ScriptAlias in apache config for unauthenticated access also. I prefer something like ScriptAlias /gitmob/ /var/www/gitolite-home/bin/gl-auth-command/ * set `$GL_HTTP_ANON_USER` to some name, like 'mob' or 'anon' in the rc file * give rights to this user ('mob' or 'anon' or whatever you used) in the gitolite config file and push the change URLs (in this example) will then look like `http://server/gitmob/reponame.git` -- we lose the userid:passwd part and change 'git' to 'gitmob'. ## ssh + http access and the `GIT_HTTP_EXPORT_ALL` variable This document only talks about setting up access to a set of git repositories purely via smart http. The `GIT_HTTP_EXPORT_ALL` variable must be set for such environments. However, it is possible to allow both ssh as well as http access, perhaps using suexec to make the CGI run under the 'git' user [detailed documentation patches welcome!] For those environments, this variable is not mandatory. If you omit that variable, you can decide which repo is accessible via http by setting `R = daemon` just for those repos. Please note that there is no way to use "deny" rules for *read* access. Do not try: repo gitolite-admin - = daemon repo @all R = daemon to achieve the (possibly common) need for disallowing http access to the admin repo. ---- Enjoy! sitaramc-gitolite-6857652/doc/index.mkd000066400000000000000000000204471171610012500177150ustar00rootroot00000000000000# F=index Hosting git repositories Gitolite allows you to setup git hosting on a central server, with fine-grained access control and many (many!) more powerful features. ## #qi quick install **If** you're comfortable with Unix and ssh, **and** you have a relatively sane setup, the following steps should work: * create a user called `git`. Login to this user. * copy your ssh pubkey from your workstation. Rename it to `YourName.pub`. * now run these commands: git clone git://github.com/sitaramc/gitolite gitolite/src/gl-system-install gl-setup -q ~/YourName.pub You're done. If it didn't work, well that's what the [install][] doc is for, especially the [if you run into trouble][insttrouble] section. **WARNING**: do **NOT** add repos or users directly on the server! You MUST manage the server by cloning the special 'gitolite-admin' repo on your workstation (`git clone git@server:gitolite-admin`), making changes, and pushing them. Here's how to [add users and repos][add]. ## #rtfm what to read... Gitolite comes with a *lot* of documentation. The master TOC (see link above) is the *only* comprehensive list of what is there, but here's an overview. * understanding gitolite * [what is gitolite][gl_what], and the rest of this document * gitolite install and basic admin in [pictures][] * gitolite and [ssh][gl_ssh] * explaining gitolite to your *users* * the [user][] document is all they need * install and setup * the "[install trouble?][insttrouble]" section, which links, among other things, to arguably the most useful doc for a newbie: [ssh troubleshooting][sts]! * maybe even the complete [install][] document * normal admin tasks done on the server * [adding users and repos][add] * [admin][]: add your own hooks, add existing repos into gitolite, etc. * [rc][]: sett gitolite behaviour options (warning: some of the variables have a security impact if you're careless) * normal admin tasks done by changing [gitolite.conf][conf] * basic access control * advanced access control * extras: personal branches, gitweb/git-daemon access, git config settings * [gitolite.conf by example][conf_examples] may also help * advanced use (experts only; you can shoot yourself in the foot nicely!) * [ADCs][]: allow users to run specific shell commands (but not give them a shell) * (also, [sample ADCs][shipped_ADCs] that come with gitolite) * let [users create][wild] their own repos and assign permissions * [delegate][deleg] admin rights * [mirror][mirroring] your servers * special installation scenarios: * use smart-[http][] instead of ssh * [migrate][migr] from gitosis Finally, [tips][] has a lot of useful information. ## #gl_what what is gitolite? Gitolite is an access control layer on top of git. Here's an "executive summary": * use a single unix user ("real" user) on the server * provide access to many gitolite users * they are not "real" users * they do not get shell access * control access to many git repositories * read access controlled at the repo level * write access controlled at the branch/tag/file/directory level, including who can rewind, create, and delete branches/tags * can be installed without root access, assuming git and perl are already installed * authentication is most commonly done using sshd, but you can also use httpd if you prefer (this may require root access). * several other neat features, too many to list here ## F=need_ why is gitolite needed? Gitolite is separate from git, and needs to be installed and configured. So... why do we bother? Gitolite is useful in any server that is going to host multiple git repositories, each with many developers, where some sort of access control is required. In theory, this can be done with plain old Unix permissions: each user is a member of one or more groups, each group "owns" one or more repositories, and using unix permissions (especially the setgid bit -- `chmod g+s`) you can allow/disallow users access to repos. But there are several disadvantages here: * every user needs a userid and password on the server. This is usually a killer, especially in tightly controlled environments * adding/removing access rights involves complex `usermod -G ...` mumblings which most admins would rather not deal with * *viewing* (aka auditing) the current set of permissions requires running multiple commands to list directories and their permissions/ownerships, users and their group memberships, and then correlating all these manually * auditing historical permissions or permission changes is pretty much impossible without extraneous tools * errors or omissions in setting the permissions exactly can cause problems of either kind: false accepts or false rejects * without going into ACLs it is not possible to give some people read-only access while some others have read-write access to a repo (unless you make it world-readable). Group access just doesn't have enough granularity * it is absolutely impossible to restrict pushing by branch name or tag name. Gitolite does away with all this: * it uses ssh magic to remove the need to give actual unix userids to developers * it uses a simple but powerful config file format to specify access rights * access control changes are affected by modifying this file, adding or removing user's public keys, and "compiling" the configuration * this also makes auditing trivial -- all the data is in one place, and changes to the configuration are also logged, so you can audit them. * finally, the config file allows distinguishing between read-only and read-write access, not only at the repository level, but at the branch level within repositories. ## why did I write it? The most important feature I needed was **per-branch permissions**. This is pretty much mandatory in a corporate environment, and is almost the single reason I started *thinking* about writing gitolite. It's not just "read-only" versus "read-write". Rewinding a branch (aka "non fast forward push") is potentially dangerous, but sometimes needed. So is deleting a branch (which is really just an extreme form of rewind). I needed something in between allowing anyone to do it (the default) and disabling it completely (`receive.denyNonFastForwards` or `receive.denyDeletes`). ### F=morefeatures_ some more features Here're some more features. * simple, yet powerful, config file syntax, including specifying gitweb/daemon access. You'll need this power if you manage lots of users+repos+combinations of access * apart from branch-name based restrictions, you can also restrict by file/dir name changed (i.e., output of `git diff --name-only`) * if your requirements are still too complex, you can split up the config file and delegate authority over parts of it * easy to specify gitweb owner, description and gitweb/daemon access * easy to sync gitweb (http) authorisation with gitolite's access config * comprehensive logging [aka: management does not think "blame" is just a synonym for "annotate" :-)] * "personal namespace" prefix for each dev * migration guide and simple converter for gitosis conf file * "exclude" (or "deny") rights at the branch/tag level * specify repos using patterns (patterns may include creator's name) * define powerful operations on the server side, even github-like forking ## security Due to the environment in which this was created and the need it fills, I consider this a "security" program, albeit a very modest one. The first person to find a hole that allows a non-admin user to push a change to a repository that he is not allowed to, will get a modest reward of 5000 INR. The hole should not require enabling any of the options listed as having a [security impact][rcsecurity] in the rc file, nor obvious things like setting the umask too loose, etc. ## contact and license Gitolite is released under GPL v2. See COPYING for details. * author: sitaramc@gmail.com, sitaram@atc.tcs.com * mailing list: gitolite@googlegroups.com * list subscribe address : gitolite+subscribe@googlegroups.com * IRC: #git and #gitolite on freenode. Note that I live in India (UTC+0530 time zone). sitaramc-gitolite-6857652/doc/install.mkd000066400000000000000000000422561171610012500202560ustar00rootroot00000000000000# F=install gitolite installation (Note: git servers are most commonly used with ssh URLs, and this document describes installing gitolite to support such usage. If your users prefer http URLs, read [this][http] to install gitolite to support "smart http"). ## installing and upgrading gitolite This section tells you how to install/upgrade gitolite, without too much background. Later sections have more details and troubleshooting info; please read them before asking for help if you have problems. A bare minimum gitolite setup has: * a server * a "hosting user" on the server (a real Unix userid; we use "git" in this document, although RPM/DEB installs use "gitolite") * a virtual "admin user" -- the user who sets up gitolite and configures it * the admin user's client or workstation, from which he does all his work Gitolite allows 3 methods of install. The two most common are (1) the **package method**, used if you have a gitolite RPM or a DEB available, and (2) the **non-root method** which is the preferred manual install mode. Less commonly used is (3) the **root method**, which is useful if you plan to have multiple "hosting users" on the same server. These install methods are described in detail below. (*Once you finish the install, read the [admin document][admin] to administer your gitolite installation*). ### F=rpmdeb package method (Unlike in the rest of this document, we use "gitolite" as the "hosting user" instead of "git" here, because that is the user that both the Fedora and Debian packages create. Your distro/OS may vary.) On your *workstation*: * copy your `~/.ssh/id_rsa.pub` file to `/tmp/YourName.pub` on the server. (The name of this file determines your gitolite username, so if you leave it as `id_rsa.pub`, your gitolite username will be `id_rsa`, which may not be what you want). On your *server*, as *root*: yum install gitolite # or 'apt-get install gitolite', or whatever # this is the only step you need to repeat when upgrading gitolite # current RPM/DEB create a hosting user called "gitolite" su - gitolite # (now as gitolite) gl-setup /tmp/YourName.pub Note: if you're running non-interactively (i.e., cannot tolerate an editor popping up), insert a "-q" before the argument to gl-setup. On your *workstation*: git clone gitolite@server:gitolite-admin ### F=nonroot non-root method **IMPORTANT WARNING -- IGNORE AT YOUR PERIL**: if you want to use this method you had better know the password to the hosting user on the server, or be able to `su` to it from root, just in case you manage to lock yourself out by messing with the keys. **NOTE**: This method is exhaustively described in the [tutorial][tut], if you're interested. (That tutorial is by someone else but it's nice enough for me to link it here). [tut]: http://sites.google.com/site/senawario/home/gitolite-tutorial On your *workstation*: * copy your `~/.ssh/id_rsa.pub` file to `/tmp/YourName.pub` on the server. (The name of this file determines your gitolite username, so if you leave it as `id_rsa.pub`, your gitolite username will be `id_rsa`, which may not be what you want). On your *server*, as *git* (the "hosting user"), first check if `$HOME/bin` is on the default PATH. If not, fiddle with the `.bashrc` or `.bash_profile` or similar files and add it somehow. Then: git clone git://github.com/sitaramc/gitolite gitolite/src/gl-system-install # defaults to being the same as: # gitolite/src/gl-system-install $HOME/bin $HOME/share/gitolite/conf $HOME/share/gitolite/hooks # to upgrade gitolite, repeat the above commands. Make sure you use the # same arguments for the last command each time. gl-setup /tmp/YourName.pub Note: if you're running non-interactively (i.e., cannot tolerate an editor popping up), insert a "-q" before the argument to gl-setup. On your *workstation*: git clone git@server:gitolite-admin #### F=upgrfromclient upgrading from from-client method to non-root method Since the from-client method is now deprecated for reasons explained elsewhere, some folks may want to do their next upgrade using the non-root method. There are many, many ways to skin this cat; here's one way: * follow non-root install but stop after the gl-system-install step * temporarily rename your `~/.gitolite.rc` file to something else * now run the gl-setup step (background: this will create a default rc file with default values, but crucially, it will give you the correct values for two very critical variables that are not used in the old from-client install method) * edit `~/.gitolite.rc` and bring in any non-default settings you may have had in your old rc file. When you're done, the only difference between your old and current rc files should be that the `$GL_PACKAGE_CONF` and the `$GL_PACKAGE_HOOKS` variables are no longer commented out and look somewhat like this: $GL_PACKAGE_CONF = '/home/git/share/gitolite/conf'; $GL_PACKAGE_HOOKS = '/home/git/share/gitolite/hooks'; Now save the file. ### F=root root method On your *workstation*: * copy your `~/.ssh/id_rsa.pub` file to `/tmp/YourName.pub` on the server. (The name of this file determines your gitolite username, so if you leave it as `id_rsa.pub`, your gitolite username will be `id_rsa`, which may not be what you want). On your *server*, as *root*: git clone git://github.com/sitaramc/gitolite gitolite/src/gl-system-install # defaults to being the same as: # gitolite/src/gl-system-install /usr/local/bin /var/gitolite/conf /var/gitolite/hooks # to upgrade gitolite, repeat the above commands. Make sure you use the # same arguments for the last command each time. # now create your "hosting user" ('git' in our examples) using whatever # command your distro expects you to use # switch to the hosting user su - git # (now as git) gl-setup /tmp/YourName.pub Note: if you're running non-interactively (i.e., cannot tolerate an editor popping up), insert a "-q" before the argument to gl-setup. On your *workstation*: git clone git@server:gitolite-admin ### #upgrade upgrading Upgrading is easy; you just re-run some of the same commands used for install. These commands are clearly noted in the install instructions above. However, if you've added any new hooks, you must also run the next step (the `gl-setup` command), although this time you don't need to supply a pubkey filename as an argument. ## #insttrouble install trouble? If you run into trouble, please read the following sections. ### common install problems The most common problem is usually ssh. Here are three facts of ssh: * ssh is a pain * most people don't know ssh well enough * even people who think they do, don't Please read how [gitolite uses ssh][gl_ssh] and the [ssh troubleshooting][sts] documents before asking for help. If you've tried multiple methods of install, you may have multiple copies of the sources lying around. This could be a problem; see [appendix a][instpath] for how to detect and deal with this. If none of this works read the rest of this document, understand it as much as you can, then ask for help. ### #instnameconv naming conventions used Throughout the documentation, we use "YourName" as the admin user, and his workstation is called "client". The hosting user is "git", and the server is called "server". **Please substitute your values as needed**. **If you're using DEB or RPM**, the installer creates a user called "gitolite", so substitute that for "git" anywhere in the docs where the "hosting user" is mentioned as "git". Also, we often say "the rc file". This means `~/.gitolite.rc` on the server. And when we say the "access control rules", or "conf file", or "config file", we mean `conf/gitolite.conf` on your gitolite-admin clone. ### F=instbg helpful background information * gitolite runs as a single (real) user on a server, and is invoked via ssh. Traditionally, this "hosting user" is "git", and thus all git URLs start with `ssh://git@server` (or the equivalent shorter form `git@server:`). * RPM/DEB create and use "gitolite" as the hosting user * there is *usually* only one hosting user per server (machine), but gitolite makes it trivial to have as many as you want. In fact, every user on the server is a potential hosting user. * using this single user and sshd (or httpd) authentication, gitolite allows you to create any number of "virtual" users. Virtual user names only mean something to gitolite, and they need not be the same as any real userid on the server or any of the clients accessing it. * the first such virtual user is the "admin user", created during the install sequence. * gitolite depends **heavily** on ssh pubkey (passwordless) access. Do not assume you know all about ssh -- most people **don't**. If in doubt, use a dedicated userid on both client and server for installation and administration of gitolite. To make matters worse, ssh problems in gitolite don't always look like ssh problems. See the [ssh troubleshooting][sts] document for help. * gitolite **does NOT** like it when people with shell access to the server fiddle with files and directories it controls. Apparently this was not obvious to some people. It is possible to have the server and the client be the same machine, and even the admin user be also the hosting user, (i.e., `sitaram@server` can install and administer a gitolite setup running under `sitaram@server`, a situation that is common with some hosting services). It's actually fairly easy and **safe** to do, **as long as you have password access to the server** for emergency use. However, I will not be documenting it because (a) if you know ssh you'll know how to extrapolate my instructions to do this and (b) if you don't know ssh it'll be a nightmare to support you. ### F=instrequire requirements #### client/workstation * git version 1.6.6 or greater * even msysgit on Windows is fine; please don't ask me for help if you're using putty, plink, puttygen, etc., for ssh; I recommend msysgit for Windows and the openssh that comes with it #### server * any Unix system with a posix compatible "sh". * people using "csh" or derivatives please don't ask me for help -- tell your admin csh is not posix compatible * git version 1.6.6 or later * can be in a non-PATH location if you are unable to install it normally; see the `$GIT_PATH` variable in the "rc" file * perl 5.8 or later * openssh or any ssh that can understand the `authorized_keys` file format (probably optional if you're using the http backend) * a Unix userid to be the hosting user, usually "git" but it can be any user, even your own normal one. (If you're using an RPM/DEB the install probably created one called "gitolite"). #### technical skills * if you're installing gitolite, you're a "system admin", like it or not. Ssh is therefore a necessary skill. Please take the time to learn at least enough to get passwordless access working. * you also need to be somewhat familiar with git itself. You cannot administer a whole bunch of git repositories if you don't know the basics of git. * some familiarity with Unix and shells is probably required * regular expressions are a big part of gitolite in many places but familiarity is not necessary to do basic access control. ### F=getgl_ getting the gitolite software You can get the latest version of gitolite from github or google code using the 'git clone' command: git clone git://github.com/sitaramc/gitolite.git # (OR) git clone https://code.google.com/p/gitolite/ #### getting a tar file from a clone If you are on an internal network and cannot clone the gitolite repo, you can do the clone on some other machine and create a tar file from it to use on the internal network. Here's how: git clone git://github.com/sitaramc/gitolite.git # (OR) git clone git://sitaramc.indefero.net/sitaramc/gitolite.git cd gitolite make master.tar # or maybe "make pu.tar" Please use the make command as shown, not a plain "git archive", because the Makefile adds a file called `.GITOLITE-VERSION` that will help you identify which version you are using. ## #instappendices_ appendixes The following sections have some miscellaneous information that does not cleanly to fit anywhere else. ### #instpath appendix a: PATH issues for gl-setup If you've tried multiple methods of install, you may have multiple copies of the sources lying around, and when you ran `gl-setup` it picked up the wrong one. This might also happen if the directory you supplied as the first argument to `gitolite/src/gl-system-install` is not even in the `$PATH`. Run `su - git` then `which gl-setup` to see which it picked up. This is what it should be for each method: * RPM/DEB method: probably `/usr/bin` * root method: the first argument to the `gitolite/src/gl-system-install` command (or `/usr/local/bin` by default) * non-root method: the first argument to the `gitolite/src/gl-system-install` command (or `$HOME/bin` by default) If this is not what you get, remove the partially installed or extraneous sources, if any, and try again. Or fix your `$PATH`. One situation that is not easy to solve is if the system admin installed gitolite using the RPM/DEB or root methods, and you want to install a later version using the non-root method. Since `/usr/bin` and `/usr/local/bin` are usually earlier than `$HOME/bin` in the `$PATH`, you'll have to get creative. Good luck. ### #clean appendix b: cleaning out a botched install When people have trouble installing gitolite, they often try to change a bunch of things manually on the server. Or sometimes they'll upgrade from one install method to another without checking some things over properly. Or they'll follow instructions meant for a much newer version of gitolite and make a royal mess of the whole thing. Here's how to clean up, without losing your actual repositories. All this is on the server. Note that the instructions are so long because they're generic enough to fit any situation. * Clean out the existing install * edit `~/.ssh/authorized_keys` and delete all lines between `# gitolite start` and `# gitolite end` inclusive. * look in `~/.gitolite.rc` for 2 variables starting with `GL_PACKAGE_`. If they are defined (and not just commented out), you need to clean out all gitolite related files and directories from those two paths. Just for reference, the defaults for a non-root install are 'conf' and 'hooks' in `$HOME/share/gitolite`, while for an RPM/DEB or root install they should be in `/var/gitolite/` or some such. If those variables don't exist or are commented out, ignore this step. * look in `$PATH` for any gitolite programs and delete them also. A good way to hunt them down is `which gl-auth-command`, and in the path you find, delete all "gl-*" programs (perhaps after checking the list, if the path you find happens to be /usr/bin or such!!) Repeat this step until there are no more. I know of people who mixed different install methods and had two, or even three, versions lying around. * make some temp directory (say "old"), and **move** the following files/directories into it: `~/.gitolite`, `~/.gitolite.rc` and `~/repositories/gitolite-admin.git`. If there's nothing you need to salvage from them you can delete them too. * if you used an RPM/DEB install, remove the package also. * Now install a fresh copy of gitolite using whatever method you prefer. (If you used a different method earlier and did not clean things out properly per the instructions given above, expect trouble). * You now have a brand new "rc" file. If your old rc file had any non-default settings you should **manually** pull them in to the new one. However, **do NOT** change the two variables starting with `GL_PACKAGE_` in the new rc file; even if the old one had something different leave them alone. * You also have a brand new gitolite-admin repo. Clone this to your workstation, then use the saved copy of the old admin repo to salvage whatever you need (entire revision history, or just the conf/key files, whatever floats your boat). Once you've got your admin repo looking how you want it, including 'repo' statements for all your existing repos, just add/commit/push it. * Go back to the server and run `gl-setup` once again (no arguments needed). That should do it. ### #uninstall_ appendix c: uninstalling gitolite completely To uninstall gitolite completely, first follow the "clean out..." steps in the previous section. If you have not really started using gitolite properly yet, you can remove all of `~/repositories` also and be done. But if you *do* need to preserve the other repos and wish to continue to use them, remove all the `update` hooks that gitolite installs in each repository. The easiest way is: find ~/repositories -wholename "*.git/hooks/update" | xargs rm -f but you can do it manually if you want to be careful. sitaramc-gitolite-6857652/doc/migrate.mkd000066400000000000000000000125011171610012500202260ustar00rootroot00000000000000# F=migr migrating from gitosis to gitolite HELP WANTED: these instructions have been revamped a bit recently [2011-07-18], so if something doesn't work let me know. [TODO: make the migration tool fix up gitweb and daemon control also...] Migrating from gitosis to gitolite is fairly easy, because the basic design is the same. There's only one thing that might trip up people: the userid. Gitosis uses `gitosis`. Gitolite can use any userid you want; most of the documentation uses `git`, while DEB/RPM packages use `gitolite`. Here are the steps on the server: * (as 'gitosis' on the server) **Rename** `~/.ssh/authorized_keys` to something else so that no one can accidentally push while you're doing this. * (as 'gitosis' on the server) For added safety, **delete** the post-update hook that gitosis-admin installed rm ~/repositories/gitosis-admin.git/hooks/post-update or at least rename it to `.sample` like all the other hooks hanging around, or edit it and comment out the line that calls `gitosis-run-hook post-update`. * (as 'gitosis' on the server) If you already use the `update` hook for some reason, **rename** it (on each individual repository that has it) to `update.secondary`. This is because gitolite uses the update hook for checking write access. * (as 'root' on the server) copy all of `~/repositories` to the gitolite hosting user's home directory. Something like cp -a /home/gitosis/repositories /home/git chown -R git.git /home/git/repositories * (as 'root' and/or 'git' on the server) Follow instructions to install gitolite; see the [install document][install]. Make sure that you **don't** change the default path for `$REPO_BASE` if you edit the config file! This will give you a gitolite config that has the required entries for the "gitolite-admin" repo. Now, log off the server and get back to the client. All subsequent instructions are to be read as "on gitolite admin's workstation". * **clone** the new gitolite-admin repo to your workstation. (You already have a clone of the gitosis-admin repo so now you have both). * **convert** your gitosis config file and append it to your gitolite config file. Substitute the path for your gitosis-admin clone in `$GSAC` below, and similarly the path for your gito**lite**-admin clone in `$GLAC`. (The gl-conf-convert program is a standalone program that you can bring over from any gitolite clone; you don't have to install all of gitolite on your workstation to use this): ./gl-conf-convert < $GSAC/gitosis.conf >> $GLAC/conf/gitolite.conf Be sure to check the file to make sure it converted correctly. Then remove the entry for the 'gitosis-admin' repo. You do not need it here and it may cause confusion. * **copy** the keys from gitosis's keydir (same meanings for GSAC and GLAC) cp $GSAC/keydir/* $GLAC/keydir If your gitosis-admin key was `you@machine.pub`, and you supplied the same one to gitolite's gl-setup program as `you.pub` when you installed gitolite, then you should remove `you@machine.pub` from the new keydir now. Otherwise you will have 2 pubkey files (`you.pub` and `you@machine.pub`) which are identical, which is *not* a good idea. Similarly, you should replace all occurrences of `you@machine.pub` with `you` in the `conf/gitolite.conf` file. * **IMPORTANT**: if you have any users with names like `user@foo`, where the part after the `@` does *not* have a `.` in it (i.e., does not look like an email address), you need to change them, because gitolite uses that syntax for [enabling multi keys][oldmultikeys]. You have two choices in how to fix this. You can change the gitolite config so that all mention of `user@foo` is changed to just `user`. Or you can change each occurrence of `user@foo` to, say, `user_foo` *and* change the pubkey filename in keydir/ also the same way (`user_foo.pub`). Just to repeat, you do NOT need to do this if the username was like `user@foo.bar`, i.e., the part after the `@` had a `.` in it, because then it looks like an email address. [This][multikey] will tell you more about these nuances. If you can understand it. * **IMPORTANT: expand any multi-key files you may have**. [Here][multikey]'s an explanation of what multi-keys are, how gitosis does them and how gitolite does it differently. You can split the keys manually, or use the following code (just copy-paste it into your xterm after "cd"-ing to your gitolite-admin repo clone): wc -l keydir/*.pub | grep -v total | grep -v -w 1 | while read a b do i=1 cat $b|while read l do echo "$l" > ${b%.pub}@$i.pub (( i++ )) done mv $b $b.done done This will split each multi-key file (say "sitaram.pub") into individual files called "sitaram@1.pub", "sitaram@2.pub", etc., and rename the original to "sitaram.pub.done" so gitolite won't pick it up. At this point you can rename the split parts more appropriately, like "sitaram@laptop.pub" and "sitaram@desktop.pub" or whatever. *Please check the files to make sure this worked properly* * Check all your changes to your gitolite-admin clone, commit, and push sitaramc-gitolite-6857652/doc/mirroring.mkd000066400000000000000000000736221171610012500206210ustar00rootroot00000000000000# F=mirroring mirroring gitolite servers Mirroring a repo is simple in git; you just need code like this in a `post-receive` hook in each repo: #!/bin/bash git push --mirror slave_user@mirror.host:/path/to/repo.git # if running gitolite, the $GL_REPO variable could be useful: # git push --mirror slave_user@mirror.host:/repo/base/path/$GL_REPO.git For a lot of people, though, mirroring is more than just 'backup', and their needs are complex enough that setup is hard. ## #mirrwhy_ why Gitolite's mirroring used to be very rigid -- one master, any number of slaves, but the slaves are identical copies of the master. No variations allowed. It's now been reworked to be much more flexible, to cater to almost any kind of setup you need. Here're some advantages: * **faster reads for everyone**: host a slave in every city you have a sizable number of developers in, and have them access their local server instead of hitting the WAN, at least for 'fetch' operations. * **faster writes for most devs**: one server doesn't have to be the master for all repos! You can choose where a repo gets "mastered" based on where the majority of that repo's users are. This was the single biggest motivation for the re-work of gitolite's mirroring; the rest of the cool stuff just happened as this feature took shape. * **transparent writes**: if all the mirrors are in your control (i.e., you trust their authentication) pushes to a slave can be transparently redirected to the master, so developers simply access their local server for everything. They don't need to know where the master is, so they're insulated from any changes you make behind the scenes. This is as close to **active-active** mirroring as you can get without worrying about race conditions and similar problems. * **partial mirroring**: all repos don't have to go all mirrors. You can choose not to mirror a repo at all, or mirror it only to certain servers. This could be due to any reason: repo too big/high-traffic for the server, repo has crypto code and the server is in a non-free country, repo has no local users at that site, or (in autonomous setups) the server admin simply doesn't want to mirror this specific repo. * **late mirroring**: if you're ok with the lag, you can have some mirrors updated only at certain times of the day, (with a simple command), instead of every time a push happens. * **autonomous mirrors**: finally, your mirrors don't have to be totally under your control. They can be owned by someone else, and you negotiate your mirroring with them. As you can see, this is a bit more than a backup solution ;-) ## RULE NUMBER ONE! **RULE OF GIT MIRRORING: users should push directly to only one server**! All the other machines (the slaves) should be updated by the master server. If a user pushes directly to one of the slaves, those changes will get wiped out on the next mirror push from the real master server. Corollary: if the primary went down and you effected a changeover, you must make sure that the primary does not come up in a push-enabled mode when it recovers. **Getting around rule number one**: see the section on "redirecting pushes" later. ## concepts and terminology Servers can host 3 kinds of repos: master, slave, and local. * A repo can be a **master** on one and only one server. A repo on its "master" server is a **native** repo, on slaves it is "non-native". * A **slave** repo cannot be pushed to by a user. It will only accept pushes from a master server. (Exception: see the "redirecting pushes" section later) * A **local** repo is not involved in mirroring at all, in either direction. ## setting up mirroring ### F=mirrcautions IMPORTANT cautions * For reasons given in the 'discussion' section later, the mirroring process will never *create* a repo on the receiving side. It has to exist, and be willing to accept pushes from the master. In particular, this means that repositories created by end-users ("wild" repos) *need to be explicitly created* on the mirror (preferably by the same user, assuming his ssh key works there as well). Once the repo has been created on the slave, subsequent pushes will be mirrored correctly. * This process will *only* mirror your git repositories, using `git push --mirror`. It will *not* mirror log files, and repo-specific files like `gl-creater` and `gl-perms` files, or indeed anything that was manually created or added (for example, custom config entries added manually instead of via gitolite). None of these affect actual repo contents of course, but they could be important, (especially the gl-creator, although if your wildcard pattern had "CREATOR" in it you can recreate those files easily enough anyway). * This document has been tested using a 3-server setup, all installed using the *non-root* method (see doc/1-INSTALL.mkd). However, the process is probably not going to be very forgiving of human error -- like anything that is this deep in "system admin" territory, errors are likely to be costly. If you're the kind who hits enter first and then thinks about what he typed, you're in for some fun times ;-) On the plus side, everything we do is done using git commands, so things are never *really* lost until you do a `git gc`. * Mirroring has *not* been, and will not be, tested with gitolite installed using the deprecated 'from-client' method. Please use one of the other methods. * Also, this has *not* been tested with smart-http. I'm not even sure it'll work; http is very fiddly to get right. If you want mirroring, at least your server-to-server comms should be over ssh. * Finally, this method uses repo-specific `git config` variables to store the mirroring information. Please read the **WARNING** in the documentation on [git config commands][rsgc] if you wish to **delete** one of those lines. ### F=mirrsetup setup and usage #### server level setup To start with, assign each server a short name. We will use 'frodo', 'sam', and 'gollum' as examples here. 1. Generate ssh keys on each machine. Copy the `.pub` files to all other machines with the appropriate names. I.e., frodo should have sam.pub and gollum.pub, etc. **Warning**: server keys are different from user keys. Do NOT attempt to (re-)use a server key for normal gitolite operations, as if the server were a normal "user"; it won't work. 2. Install gitolite on all servers, under some 'hosting user' (we'll use `git` in our examples here). You need not use the same hosting user on all machines. It is not necessary to use the same "admin key" on all the machines. However, if you do plan to mirror the gitolite-admin repo also, they will eventually become the same anyway. In our example, frodo does mirror the admin repo to sam, but not to gollum. (Can you really see frodo or sam trusting gollum?) 3. Now copy `hooks/common/post-receive.mirrorpush` from the gitolite source, and install it as a custom hook called `post-receive`; see [here][customhooks] for instructions. 4. Edit `~/.gitolite.rc` on each machine and add/edit the following lines. The `GL_HOSTNAME` variable **must** have the correct name for that host (frodo, sam, or gollum), so that will definitely be different on each server. The other line can be the same, or may have additional patterns for other `git config` keys you have previously enabled. See [here][rsgc] and the description for `GL_GITCONFIG_KEYS` in [this][rcsecurity] for details. $GL_HOSTNAME = 'frodo'; # will be different on each server! $GL_GITCONFIG_KEYS = "gitolite.mirror.*"; (Remember the "rc" file is NOT mirrored; it is meant to be site-local). Note: if `GL_HOSTNAME` is undefined, you cannot push to repos which have the 'gitolite.mirror.master' config variable set. (See 'details' section below for more info on this variable). > If you wish, you can also add this hostname information to the > `GL_SITE_INFO` variable in the rc file. See the rc file documentation > for more on that. 5. On each machine, add the keys for all other machines. For example, on frodo you'd run these two commands: gl-tool add-mirroring-peer sam.pub gl-tool add-mirroring-peer gollum.pub 6. Create "host" aliases on each machine to refer to all other machines. See [here][sshhostaliases] for what/why/how. The host alias for a host (in other machines' `~/.ssh/config` files) MUST be the same as the `GL_HOSTNAME` in the referred host's `~/.gitolite.rc`. Gitolite mirroring **requires** this consistency in naming; things will NOT work otherwise. For example, if machine A's `~/.gitolite.rc` says `$GL_HOSTNAME = 'frodo';`, then all other machines must use a host alias of "frodo" in their `~/.ssh/config` files to refer to machine A. Once you've done this, each host should be able to reach the other hosts and get a response back. For example, running this on sam: ssh frodo info should get you Hello sam, I am frodo. Check this command from *everywhere to everywhere else*, and make sure you get expected results. **Do NOT proceed otherwise.** #### repository level setup Setting up mirroring at the repository level instead of at the "entire server" level gives you a lot of flexibility (see "discussion" section below). The basic idea is to use `git config` variables within each repo (gitolite allows you to create them from within the gitolite.conf file so that's convenient), and use these to specify which machine is the master and which machines are slaves for the repo. Let's say frodo and sam are internal servers, while gollum is an external (and therefore less trusted) server that has agreed to help us out by mirroring one of our high traffic repos. We want the following setup: * the "gitolite-admin" repo, as well as an internal project repo called "ip1", should be mastered on frodo and mirrored to sam. * internal project "ip2" has almost all of its developers closer to sam, so it should be mastered there, and mirrored on frodo. * an open source project we manage, "os1", should be mastered on frodo and mirrored on both sam and gollum. So here's how our example would go: 1. Clone frodo's and sam's gitolite-admin repos to your workstation, then add the following lines to both their gitolite.conf files: repo ip1 gitolite-admin config gitolite.mirror.master = "frodo" config gitolite.mirror.slaves = "sam" repo ip2 config gitolite.mirror.master = "sam" config gitolite.mirror.slaves = "frodo" You also need normal access control lines for ip1 and ip2; I'm assuming you already have them elsewhere, at least on frodo. (What you have on sam won't matter in a few minutes, as you will see!) Commit and push these changes. 2. There are a couple of quirks to keep in mind when you make changes to the gitolite-admin repo's config. * the first push will create the `git config` entries required, but by then it is too late to *act* on them; i.e., actually do the mirroring. If there were any older values, like a different list of slaves perhaps, then those would be in effect. This is largely because git invokes post-receive before post-update. In theory I can work around this but I do not intend to. Anyway, this means that after the 2 pushes, you have to make a dummy push from frodo: git commit --allow-empty -m empty; git push which gets you something like this amidst the other messages: remote: (25158&) frodo ==== (gitolite-admin) ===> sam telling you that frodo is sending gitolite-admin to sam in the background. * the second quirk is that your clone of server sam's gitolite-admin repo is now completely out of date, since frodo has overwritten it on the server. You have to 'cd' to that clone and do this: git fetch git reset --hard origin/master 2. That completes the setup of the gitolite-admin and the internal project repos. We'll now setup things for the open source project, "os1". On frodo's gitolite-admin clone, add the following lines to `conf/gitolite.conf`, then commit and push: repo os1 config gitolite.mirror.master = "frodo" config gitolite.mirror.slaves = "sam gollum" Also, send the same lines to gollum's administrator and ask him to add them into his conf/gitolite.conf file, commit, and push. ### F=mirrsync commands to (re-)sync mirrors You don't have to put all the slaves in `gitolite.mirror.slaves`. For example, let's say you have some repos that are very active, and two of your mirrors that are halfway across the world are getting pushed very frequently. But you don't need those mirrors to be that closely updated, perhaps *because* they are halfway across the world and those guys are asleep ;-) Or maybe there was a network glitch and even the default slaves are now lagging, so they need to be manually synced. Or a slave realised that one of its repos is lagging for some reason, and wants to request an immediate update. Whatever the reason, you need ways to sync a repo from a command line. Here are ways to do that: 1. On the master server, you can start a **background** job to mirror a repo. The command/syntax is gl-mirror-shell request-push reponame [list of keys/slaves] The list at the end is optional, and can be a mix of slave names or your own gitolite mirror config keys. (Yes, you can have any key, named anything you like, as long as it starts with `gitolite.mirror.`). If the list is not supplied, the `gitolite.mirror.slaves` key is used. Keys can have values that in turn contain a list of keys/slaves. The list is recursively *expanded* but recursion is not *detected*. Order is preserved while duplicates are removed. If you didn't get that, see the example :-) **Warning**: the `gitolite.mirror.slaves` key should have only hosts, no keys, in it. The program exits with a return value of "1" if it found no slaves in the list passed, otherwise it fires off the background job, prints an informative message, and exits with a return value of "0". We'll take an example. Let's say your gitolite config file has this: repo ip1 config gitolite.mirror.master = "frodo" config gitolite.mirror.slaves = "sam merry pippin" config gitolite.mirror.hourly = "sam legolas" config gitolite.mirror.nightly = "gitolite.mirror.hourly gimli" config gitolite.mirror.all = "gitolite.mirror.nightly gitolite.mirror.hourly gitolite.mirror.slaves" Then the following commands have the results described in comments: gl-mirror-shell request-push ip1 # which is the same as: gl-mirror-shell request-push ip1 gitolite.mirror.slaves # pushes to sam, merry, pippin gl-mirror-shell request-push ip1 gollum # pushes only to gollum. Note that gollum is not a member of any of # the slave lists we defined. gl-mirror-shell request-push ip1 gitolite.mirror.slaves gollum # pushes to sam, merry, pippin, gollum gl-mirror-shell request-push ip1 gitolite.mirror.slaves gitolite.mirror.hourly # pushes to sam, merry, pippin, legolas gl-mirror-shell request-push ip1 gitolite.mirror.all # pushes to sam, legolas, gimli, merry, pippin The last two examples show recursive expansion with order-preserving duplicate removal (hey there's now a published conference paper on gitolite, so we have to use jargon *somewhere* or they won't accept follow-on papers!). If you do something like this: config gitolite.mirror.nightly = "gimli gitolite.mirror.nightly" or this: config gitolite.mirror.nightly = "gimli gitolite.mirror.hourly" config gitolite.mirror.hourly = "legolas gitolite.mirror.nightly" you deserve what you get. 2. If you want to start a **foreground** job, the syntax is `gl-mirror-shell request-push ip1 -fg gollum`. Foreground mode requires one (and only one) slave name -- you cannot send to an implicit list, nor to more than one slave. 3. Cronjobs and custom mirroring schemes are now very easy to do. Use either of the command forms above and write a script around it. Appendix A contains an example setup. 4. Once in a while a slave will realise it needs an update, and wants to ask for one. It can run this command to do so: ssh sam request-push ip2 If the requesting server is not one of the slaves listed in the config variable gitolite.mirror.slaves on the master, it will be rejected. This is always a foreground push, reflecting the fact that the slave may want to know why their push errored out or didn't work last time or whatever. ## #ad/m-dtls details ### F=mirrconf the `conf/gitolite.conf` file One goal I have is to minimise the code changes to "core" gitolite due to this, so all repo-specific mirror settings are stored as `git config` variables (you know you can specify git config variables in the gitolite config file right?). These are: * `gitolite.mirror.master` The name of the server which is the master for this repo. Each server will compare this with `$GL_HOSTNAME` (from its own rc file) to determine if it's the master or a slave. Here're the possible values: * **undefined** or `local`: this repo is local to this server * **same** as `$GL_HOSTNAME`: this server is the "master" for this repo. (The repo is "native" to this server). * **not same** as `$GL_HOSTNAME`: this server is a "slave" for the repo. (The repo is a non-native on this server). * `gitolite.mirror.slaves` Ignored for non-native repos. For native repos, this is a space-separated list of servers to push to from the `post-receive` hook. Clearly, you can have different sets of slaves for different repos (again, see "discussion" section later for more on this). * `gitolite.mirror.redirectOK` See the section on "redirecting pushes" * In addition, you can create your own slave lists, named whatever you want, except they have to start with `gitolite.mirror.`. The section on "commands to (re-)sync mirrors" has some examples. ### F=mirrredirect redirecting pushes **Please read carefully; there are security implications if you enable this for mirrors NOT under your control**. When a user pushes to a non-native repo, it is possible to transparently redirect the push to the correct master server. This is a very neat feature, because now all your users just use one URL (the mirror nearest to them). They don't need to know where the actual master is, and more importantly, if you and the other admins change it, they don't need to know it changed! The `gitolite.mirror.redirectOK` config variable decides where this redirection is OK. If it is set to 'true', any valid 'slave' can redirect an incoming non-native push from a developer. Otherwise, it contains a list of slaves that are permitted to redirect pushes (this might happen if you don't trust some of your slaves enough to accept a redirected push from them). **Warning**: like `gitolite.mirror.slaves`, this key also should have only hosts, no keys, in it. This check needs to pass on both the master and slave servers; both have a say in deciding if this is allowed. (The master may have real reasons not to allow this; see below. I cannot think of any real reason for the *slave* to disable this, but it's there in case some admin doesn't like it). There are some potential issues that you MUST consider before enabling this: * (security) If the slave and master server are so different or autonomous that a user, say "alice", on the slave is not guaranteed to be the same one as "alice" on the master, then the master admin should NOT enable this feature. This is because, in this scheme, authentication happens on the slave, but authorisation is on the master. The slave-authenticated userid (alice) is passed to the master. (If you know ssh well enough, you know that the ssh authentication has already happened, so all we can do is ensure authorisation happens with whatever username we know so far). * If your slave is out of sync with the master for whatever reason, then the user will get confusing results. A `git fetch` may say everything is upto-date but the push fails saying it is not a fast-forward push. (Of course there's a way to fix this; see the "commands to (re-)sync mirrors" section above). * We cannot redirect non-git commands like ADC, setperms, etc because we don't really have a way of knowing what repo he's talking about (different commands have different syntaxes, some have more than one reponame...). Any user who needs to do that should access the end server directly. It should be easy enough to write an ADC to do the forwarding, in case the slave server is the only one that can reach the real master due to network or firewall setup. Ideally, I recommend that ad hoc repos not be mirrored at all. Keep mirroring for "blessed" repos only. ## example setups Here are some samples of what is possible. ### F=mirrnonauto non-autonomous In this setup, the slave server is under the same "management" as the master. All repos, including gitolite-admin are mirrored, and *each slave is an exact replica of the master*. Since the admin repo is mirrored, authentication info is identical across all servers, and it is safe to use redirected pushes. (This was the only type of mirroring possible in the old mirroring code in gitolite). Install gitolite on all servers. Then add these lines to the top of all admin repos and push them all. This sets up the config for mirroring all repos. repo @all config gitolite.mirror.master = "frodo" config gitolite.mirror.slaves = "sam gollum" Once they're all pushed, sync the admin repo once: # on master server gl-mirror-shell request-push gitolite-admin Since authentication is also being mirrored, you can take advantage of redirected pushing if you wish: repo @all config gitolite.mirror.redirectOK = "true" ### F=mirrnonautolocal non-autonomous with local repos As above, but you want to allow each slave server to have some repos be "local" to the server (not be mirrored), for whatever reason. Different slaves may have different needs, so this really means that the same gitolite.conf should behave differently on each server -- something which till now was impossible. Well what's life without a new feature once in a while? The string "HOSTNAME" is now specially treated in an include filename. If it is seen without any alphanumeric characters or underscores next to it on either side, it is replaced by the value of `$GL_HOSTNAME`. Setup the config as in the previous setup except that you shouldn't use repo @all now; instead, you'll have to name the repos to be mirrored in some way. Make sure gitolite-admin is in this list. Complete the mirror setup (including the first-time sync command) like before. Now add the line include "HOSTNAME.conf" to the end of conf/gitolite.conf, and create new files, conf/frodo.conf, conf/sam.conf, etc., with appropriate content. That's it. When this config is pushed, each machine will have an effective config that consists of the main file, with the correct HOSTNAME.conf included (and all the others ignored) when the include statement is reached. ### F=mirrsemiauto semi-autonomous So far, the "central" admin still has control over the gitolite.conf file and all repos created. Sometimes it's easier to give control over parts of the configuration to people at the mirror sites. To keep it simple, each admin will be able to do whatever they want to directories within a subdirectory of the same name as the hostname. You can combine the "HOSTNAME" feature above with [delegation][deleg]. Let's say the admin for sam is a user called "gamgee", and the admin for gollum is "smeagol". Add this to your conf file: @sam = sam/..* @gollum = gollum/..* Then use NAME/ rules (see the delegation doc for details) and allow gamgee to write only conf/sam.conf, and smeagol to write only conf/gollum.conf. Now in the main config file, at the end (or wherever you wish), add one line: subconf "HOSTNAME.conf" ### F=mirrauto autonomous In many ways this is the simplest setup. The slave server belongs to someone else. Their admin has final say on what goes into their gitolite-admin repo and thus their server's config. The gitolite-admin repo is NOT mirrored, and mirroring of individual repos (i.e., actual config lines included) is by negotiation/agreement between the admins. Authentication info is not common. The master has no real control over who can read the repos on the slave. Allowing redirected pushes is not a good idea, unless you have other means of trust (administrative, contractual, legal, etc.) Best for open source projects with heavy "fetch" load compared to "push". ## F=mirrdisc discussion ### problems with the old mirroring model The old mirroring model had a single server as the master for *all* repositories. Slaves were effectively only for load-balancing reads, or for failover if the master died. This is not good enough for corporate setups where the developers are spread fairly evenly across the world. Some repos need to be closer to some teams (NUMA is a good analogy). A model where different repos are "mastered" in different cities is much more efficient here. The old model had other rigidities too, though they're not really *problems*, as such: * the slaves are just slaves; they can't have any "local" repos. * a slave had to carry *all* repos; it couldn't choose to carry just a subset. * it implicitly assumed all the mirrors were under the same admin, and that the gitolite-admin repo was itself mirrored too. ### the new mirroring model In the new model, servers can be much more independent and autonomous than in the old model. (Don't miss the side note in the 'repository level setup' section if you prefer the old model). The new model has a few pros and cons. The pros come from the flexibility and freedom that mirrors servers get, and the cons come from authorisation being more rigorously checked (for example, a slave will only accept a push if *its* configuration also says that the sending server is indeed the master for this repo). * A mirroring operation will not *create* a repo on the mirror; it has to exist before a push happens on the master. Typically, the admin on the slave must create the repo by adding the appropriate lines in his config. If your setup is not autonomous (i.e., you're mirroring the admin repo as well) then this happens automatically for normal repos. However, *wildcard repos still won't work as seamlessly as in the old model*; see the first bullet in the 'IMPORTANT cautions' section earlier. * The gitolite-admin repo (and config) need not be mirrored. This allows the slave server admin to create site-local repos, without forcing him to create a second gitolite install for them. (Site-local repos are useful for purely local projects that need not/should not be mirrored for some reason, or ad-hoc personal repos that developers create for themselves, etc.) * Servers can choose to mirror a subset of the repos from one of the bigger servers. In the open source world, you can imagine more popular repos (or more popular parts of huge projects like KDE) having more mirrors. Or substitute "more popular" with "larger in size" if you wish (FlightGear-data anyone?) In the corporate world it could help with jurisdiction issues if the mirror is in a different country with different laws. I'm sure people will find other uses for this. And I'm *positive* the pros will outweigh the cons. If you don't like it, follow the suggestion in the side note somewhere up above, and just forget this feature exists :-) ## appendices ### F=mirrcron appendix A: example cronjob based mirroring Let's say you have some repos that are very active. You're pushing halfway across the world every few seconds, but those slaves do not need to be that closely updated, perhaps *because* they are halfway across the world and those guys are asleep ;-) You'd like to update them once an hour instead. Here's how you might do that. First add this line to the configuration for those repos: config gitolite.mirror.hourly = "slave1 slave2 slave3" Then write a cron job that looks like this (untested). #!/bin/bash REPO_BASE=`${0%/*}/gl-query-rc REPO_BASE` cd $REPO_BASE find . -type d -name "*.git" -prune | while read r do # get reponame as gitolite knows it r=${r:2} r=${r%.git} gl-mirror-shell request-push $r gitolite.mirror.hourly # that command backgrounds the push, so you'd best wait a few seconds # before hitting the next one, otherwise you'll have all your repos # going out at once! sleep 10 done ### F=mirrparanoia appendix B: efficiency versus paranoia If you're paranoid enough to use mirrors, you should be paranoid enough to use the `receive.fsckObjects` setting. However, informal tests indicate a 40-50% CPU overhead from this. If you're ok with that, make the appropriate adjustments to `GL_GITCONFIG_KEYS` in the rc file, then add this to your gitolite.conf file: repo @all config receive.fsckObjects = "true" Personally, I just set `git config --global receive.fsckObjects true`, since those servers aren't doing anything else anyway, and are idle for long stretches of time. It's upto you what you want to do here. sitaramc-gitolite-6857652/doc/mob-branches.mkd000066400000000000000000000053021171610012500211370ustar00rootroot00000000000000# F=mob mob branches in gitolite WARNING: This is hairy stuff. But what's life without a little danger? WARNING 2: girocco does mob branches quite differently; the controls on what a mob branch can do are much more fundamental. Here we just trick gitolite into accepting anonymous ssh connections and pretending they're from a mythical user called "mob". **This means all the access control is -- as you might expect -- in the gitolite.conf file, so make sure you don't give the `mob` user too many rights!** (tested on Fedora 14; assumes your gitolite server userid is "gitolite" and install was "non-root" method; adjust according to your environment. If you need more than this, you should not be enabling mob branches anyway ;-) [hah! Easy way out of being badgered with questions!] * create a file called `/tmp/mobshell` (put it somewhere more permanent if you wish). This file should be `chmod +x` and contain #!/bin/sh shift export SSH_ORIGINAL_COMMAND SSH_ORIGINAL_COMMAND="$*" /home/gitolite/bin/gl-auth-command mob # see one of the lines in ~gitolite/.ssh/authorized_keys for the # precise location of the gl-auth-command script * create a user called mob. Give it the same UID number and `$HOME` as your gitolite server userid, and set the login shell to be the script you just created. Also delete the password. id -u gitolite # returns 503 or something... useradd -d /home/gitolite -s /tmp/mobshell -u 503 -o mob passwd -d mob * make sure you have a recent enough sshd and put these lines at the bottom, then restart sshd Match user mob PermitEmptyPasswords yes That's it. Now you can add stuff to your gitolite.conf file. Here's some examples: * This allows the mob user to do anything to the "mob" branch: repo foo RW+ = alice bob R = eve RW+ mob$ = mob # only the mob branch, nothing more * This is the same, except it can be any branch under "mob/" so you get some flexibility: RW+ mob/ = mob * Girocco allows pushing to the mob branch only if it already exists (that is, the mob user cannot *create* the mob branch, but if it already exists he can push to it). Here's how you'd do that in gitolite: repo foo RW+C = alice bob R = eve RW+ mob$ = mob * This gives *every* repo a mob branch (be careful!) repo @all RW+ mob$ = mob How do mob users access it? The URLs just look like: `mob@server:repo` instead of `gitolite@server:repo` That's it! sitaramc-gitolite-6857652/doc/nagp.mkd000066400000000000000000000253121171610012500175270ustar00rootroot00000000000000# F=nagp ...not a gitolite problem! Subtitle: Unix, ssh, git, and gitolite -- recognising the boundaries **Warning**: Most of this is technical, but some of it is definitely subjective opinion. More and more people are being tasked with creating a "server" environment for git, (and thus being forcibly introduced to gitolite), before they've had a chance to know git (or even Unix) well enough. As a result, I often get questions that have **nothing** to do with gitolite, because people don't know where the boundaries are. Here're some facts about gitolite: * gitolite runs on the server. To the client it looks just like any other ssh-based URL that is using public keys. * once you connect, it looks just like any other [bare repo][bare] on a server for normal git operations (clone, fetch, push). Users won't even *know* it exists if they do only what they're allowed to. (I.e., it becomes visible only when it has to deny access for some operation) * even "on disk", except for reserving the `update` hook for its own purposes and possibly a couple of extra files, the contents of a gitolite-managed bare repo are the same as a normal bare repo. A good short term measure is to read this [simple git session][sgs] for insights into the relationship between the "server" and the "client" in git. That might help with some of the problems described in this document. [bare]: http://sitaramc.github.com/concepts/0-terminology.html#working_tree_repository_bare_repository [sgs]: http://sitaramc.github.com/1-basic-usage/simple-git-session.html ## ssh Let's get this out of the way first. The *superstar* of the "not a gitolite problem" category is actually ssh. Surprised? It is so common that it has [its own document][auth] to tell you why it is *not* a gitolite problem, while [another one][sts] tries to help you anyway! ## PATH issues This is actually related to ssh. When you run commands over ssh, they run *non*-interactively, and depend on what your shell is, etc., certain shell startup files are not executed, so some environment variables may be different than what you see when you log in interactively. Example: Redmine/Chiliproject using RVM and redmine git hosting can't run post-receive hook. [This](https://github.com/ericpaulbishop/redmine_git_hosting/issues/125) has more info on this specific issue. It is also useful to remember that *gitolite does not fiddle with the PATH except for adding the `GIT_PATH` variable if you defined it*. ## git * first push to a new repo Even if the error message said "the remote end hung up unexpectedly", this is not a gitolite problem. If you read the full message, it will probably be something like this: No refs in common and none specified; doing nothing. Perhaps you should specify a branch such as 'master'. fatal: The remote end hung up unexpectedly error: failed to push some refs to '/tmp/tmp-repos/bare.git' (To be fair, this message is still not clear enough; even a careful reader might end up trying `git push master` instead of `git push origin master`.) * client-side versus server-side hooks Just because you saw something in gitolite's docs about hooks, it doesn't mean gitolite can make a "post-checkout" hook work! You need to read man githooks again to understand the difference between hooks that run on your local repo in response to local commands (like commit, checkout), and remote hooks that run on the server in response to a client-initiated operation (like push). An equally wrong expectation is that a clone or a pull will also bring along hooks from the server! Sorry, but there is nothing in git that allows the *server* to do something on the *client* -- if there were, it would be a huge security issue. What you can do in gitolite is to write yourself a hook that checks every commit in a push for compliance and rejects the push (*all* the commits in the push, mind!) if it doesn't comply. However, other than telling you how to get your hook to trigger, the actual code in that hook is out of scope for me. * Jenkins integration I don't know much about CI systems, but I'm pretty sure they run off of some hook or other, but gitolite may already be using those hooks. The section on hook chaining [here][hookchaining] shows you how to run your own hooks. In short, CI integration is no more a gitolite problem than any other purpose for which git's hooks can be used. ## windows I'm *interested* in making sure it works fine with Windows, simply because I have colleagues at work who use it. But that doesn't mean I can help you; I just don't know enough to help you. (And if you even breathe the words "putty" or "plink" I will totally tune you out!) I do have people using it happily on Windows with Eclipse, Visual Studio, and God alone knows what else, so I know it *can* (be made to) work. So, hang in there... it'll all work out eventually. ## apple Weirdly enough, this is the one thing that Steve Ballmer and I probably agree on, so I won't elaborate on that ;-) It seems to me though, that many recent reports of "weird" behaviour reported have come from Macs. Yet another reason for me to back off with an apology. ## just say NO! These are the things I won't do, for various reasons, mostly technical, with a smattering of some subjective stuff. If you've been hit by one of these, and disagree with me -- well that's why gitolite is GPL. As long as you satisfy the GPL, you can simply "fork off" ;-) * using a database backend Someone wanted help rewriting gitolite to use a database instead of a perl hash. I think that's a very bad idea. Show me an install where gitolite does not scale and I'll fix it without using a damn database. I *chose* the perl hash very deliberately and for very specific reasons; using a DB would kill all that. * symlinks within `REPO_BASE` Someone wanted to put the actual repos somewhere else and create a symlink to them inside gitolite's `REPO_BASE`. No. Gitolite assumes all of `REPO_BASE` is in its control, and nothing else outside is (barring the admin directory `~/.gitolite` and the rc file `~/.gitolite.rc`). You could argue that this is secure enough if the admin knows what he is doing, but I'm not buying that. Some odd screwup involving symlinks will show up some day and gitolite will get blamed. * deleting environment variables copied from client session This same guy wanted me to add code to delete certain environment variables at startup because "the openssh servers in the linux distribution that [he] use[s], are configured to copy `GIT_*` variables to the remote session". This is wrong on so many levels it's almost plonk-able! * using `cp` instead of `ln` Guy has an NTFS file system mounted on Linux. So... no symlinks (an NTFS file system on Windows works fine because msysgit/cygwin manage to *simulate* them. NTFS mounted on Linux won't do that!) He wanted all the symlink stuff to be replaced by copies. No. Way. * non-bare repos on the server Some guy gave me a complicated spiel about git-svn not liking bare repos or whatever. I tuned off at the first mention of those 3 letters so I don't really know what the actual problem was. But it doesn't matter. Even if someone (Ralf H) had not chipped in with a workable solution, I still would not do it. A server repo should be bare. Period. ### behind my back Some of the "Just say NO" items are from situations where someone or something changes stuff behind gitolite's back. I am particularly unsympathetic to this sort of thing. * incomplete ownership of `REPO_BASE` This guy had a repo-base directory where not all of the files were owned by the git user. As a result, some of the hooks did not get created. He claimed my code should detect OS-permissions issues while it's doing its stuff. No. I refuse to have the code constantly look over its shoulder making sure fundamental assumptions are being met. * empty template directory (See man git-init for what a template directory is). The same guy with the symlinks and the environment variables (so this is his third appearance in this list!) had an empty template directory because he "does not like to have sample hooks in every repository". So naturally, the hooks directory does not get created when you run a `git init`. He expects gitolite to compensate for it. Granted, it's only a 1-line change. But again, this falls under "constantly looking over your shoulder to double check fundamental assumptions". Where does it end? * per-repo umask setting Some people believe that gitolite should have code to compensate for a "potential" failure of gitweb or httpd to restrict access as designed. Sorry. Not in "core" at least. This is yet another of those "constantly looking over your shoulder" examples. Why should it be gitolite's problem to compensate if gitweb or httpd misbehave? Yes, I know all about layers of security and defense in depth, thank you but no. > I gave them a solution that involves adding `config > core.sharedRepository = 0750` in the gitolite.conf file for those > repos, then doing a one-time fixup for newly created repos. But they > decided they needed to patch gitolite and they're going with it. > I'm not too worried. These guys ran a HEAVILY patched gitosis for > years; they're probably used to it. ## that's outrageous This section is for really outrageous stuff. * clearing out a repo the long/wrong way This guy tells me he often needs to clear out a repo. How? He first manually deletes the repo on the server, then pushes a dummy commit to the admin repo to let gitolite re-create it. Of course, this second step is "annoying" so he mailed me and told me there has to be a better way! I asked him why he didn't try `git push -f` and he said he didn't know he could do that. I'm NOT joking. I wish I were! (Yes, we all know that `git push -f` is not the same as delete/re-create, but the point is that he really didn't need it; he just assumed that's the only way to force his commits to the remote.) * can connect to github, can't connect to gitolite So this mac-fan went on about his great IDE (CodeX or something) and how it works with github but no luck with gitolite. Therefore he comes and asks on \#gitolite. (I give him bonus points for telling people on TWO channels that it is "THE best ide"). The actual error? "host unreachable". In usenet terms... `*PLONK*`! sitaramc-gitolite-6857652/doc/packaging.mkd000066400000000000000000000040221171610012500205210ustar00rootroot00000000000000# F=packaging packaging gitolite Here's how you'd package gitolite. In the following description, location "X" can be, say, `/usr/share/gitolite/conf` or some such, and similarly location "Y" can be perhaps `/usr/share/gitolite/hooks`. It's upto your distro policies where they are. **Step 1**: Clone the gitolite repo and run the make command inside the clone git clone git://github.com/sitaramc/gitolite.git cd gitolite make pu.tar # or "make master.tar" or "make v1.2.tar" etc Then you explode the tar file in some temporary location. *Alternatively, you can `git checkout` the tag or branch you want, and run this command in the clone directly*: git describe --tags --long > conf/VERSION **Step 2**: Now make the following changes (no trailing slashes in the location values please): * `src/gl-setup` should have the following line: GL_PACKAGE_CONF="X" * `conf/example.gitolite.rc` should have the following lines: $GL_PACKAGE_CONF="X"; $GL_PACKAGE_HOOKS="Y"; **Step 3**: Move (or arrange to move) the files to their proper locations as given below: * everything in "src" goes somewhere on the PATH * everything in "conf" goes to location "X" * everything in "hooks" goes to location "Y" **Step 4**: There is no step 4. Unless you count telling your users to run `gl-setup` as a step :) On the initial install (urpmi, yum install, or apt-get install), you could also choose to setup a userid called "gitolite", and run "gl-setup" as that user; however I do not know how you would come up with the initial pubkey that is needed. Anyway, the point is that the "gitolite" user is no more special than any other in terms of hosting gitolite. Any user can host gitolite on his userid by just running "gl-setup". When you upgrade, just overwrite all the files; it'll all just work. In fact, other than the initial "gl-setup" run, the only time a gitolite hosting user has to actually do anything is to edit their own `~/.gitolite.rc` file if they want to enable or disable specific features. sitaramc-gitolite-6857652/doc/progit-article.mkd000066400000000000000000000323441171610012500215320ustar00rootroot00000000000000# F=progit (master copy of progit chapter on gitolite) ## Gitolite ## Note: the latest copy of this section of the ProGit book is always available within the [gitolite documentation][progit]. The author would also like to humbly state that, while this section is accurate, and *can* (and often *has*) been used to install gitolite without reading any other documentation, it is of necessity not complete, and cannot completely replace the enormous amount of documentation that gitolite comes with. Git has started to become very popular in corporate environments, which tend to have some additional requirements in terms of access control. Gitolite was originally created to help with those requirements, but it turns out that it's equally useful in the open source world: the Fedora Project controls access to their package management repositories (over 10,000 of them!) using gitolite, and this is probably the largest gitolite installation anywhere too. Gitolite allows you to specify permissions not just by repository, but also by branch or tag names within each repository. That is, you can specify that certain people (or groups of people) can only push certain "refs" (branches or tags) but not others. ### Installing ### Installing Gitolite is very easy, even if you don't read the extensive documentation that comes with it. You need an account on a Unix server of some kind; various Linux flavours, and Solaris 10, have been tested. You do not need root access, assuming git, perl, and an openssh compatible ssh server are already installed. In the examples below, we will use the `gitolite` account on a host called `gitserver`. Gitolite is somewhat unusual as far as "server" software goes -- access is via ssh, and so every userid on the server is a potential "gitolite host". As a result, there is a notion of "installing" the software itself, and then "setting up" a user as a "gitolite host". Gitolite has 3 methods of installation. People using Fedora or Debian systems can obtain an RPM or a DEB and install that. People with root access can install it manually. In these two methods, any user on the system can then become a "hosting user". However, most people don't want/need to install gitolite system-wide -- they can just install it within one specific hosting user. We will describe this last method in this article; for the other methods please see the documentation. To begin, create a user called `git` on your server and login to this user. Copy your ssh pubkey (a file called `~/.ssh/id_rsa.pub` if you did a plain `ssh-keygen` with all the defaults) from your workstation, renaiming it to `YourName.pub`. Then run these commands: git clone git://github.com/sitaramc/gitolite gitolite/src/gl-system-install gl-setup -q ~/YourName.pub # for example, I would run 'gl-setup -q ~/sitaram.pub' Finally, back on your workstation, run `git clone git@server:gitolite-admin`. And you're done! Gitolite has now been installed on the server, and you now have a brand new repository called `gitolite-admin` in your workstation. You administer your gitolite setup by making changes to this repository and pushing. See [adding users and repos][add] to start with. ### Customising the Install ### While the default, quick, install works for most people, there are some ways to customise the install if you need to. If you omit the `-q` argument, an editor pops up with a file for you to edit, so you can change certain server-side parameters, such as the location of the actual repositories. This "rc" file is documented in [doc/gitolite.rc.mkd][rc] so you should be able to make any changes you need quite easily, save it, and continue. ### Config File and Access Control Rules ### Once the install is done, you switch to the `gitolite-admin` repository (placed in your HOME directory) and poke around to see what you got: $ cd ~/gitolite-admin/ $ ls conf/ keydir/ $ find conf keydir -type f conf/gitolite.conf keydir/sitaram.pub $ cat conf/gitolite.conf #gitolite conf # please see doc/gitolite.conf.mkd for details on syntax and features repo gitolite-admin RW+ = sitaram repo testing RW+ = @all Notice that "sitaram" (the name of the pubkey in the gl-setup command you used earlier) has read-write permissions on the `gitolite-admin` repository as well as a public key file of the same name. The config file syntax for gitolite is documented in [doc/gitolite.conf.mkd][conf] so we'll only mention some highlights here. You can group users or repos for convenience. The group names are just like macros; when defining them, it doesn't even matter whether they are projects or users; that distinction is only made when you *use* the "macro". @oss_repos = linux perl rakudo git gitolite @secret_repos = fenestra pear @admins = scott # Adams, not Chacon, sorry :) @interns = ashok # get the spelling right, Scott! @engineers = sitaram dilbert wally alice @staff = @admins @engineers @interns You can control permissions at the "ref" level. In the following example, interns can only push the "int" branch. Engineers can push any branch whose name starts with "eng-", and tags that start with "rc" followed by a digit. And the admins can do anything (including rewind) to any ref. repo @oss_repos RW int$ = @interns RW eng- = @engineers RW refs/tags/rc[0-9] = @engineers RW+ = @admins The expression after the `RW` or `RW+` is a regular expression (regex) that the refname (ref) being pushed is matched against. So we call it a "refex"! Of course, a refex can be far more powerful than shown here, so don't overdo it if you're not comfortable with perl regexes. Also, as you probably guessed, Gitolite prefixes `refs/heads/` as a syntactic convenience if the refex does not begin with `refs/`. An important feature of the config file's syntax is that all the rules for a repository need not be in one place. You can keep all the common stuff together, like the rules for all `oss_repos` shown above, then add specific rules for specific cases later on, like so: repo gitolite RW+ = sitaram That rule will just get added to the ruleset for the `gitolite` repository. At this point you might be wondering how the access control rules are actually applied, so let's go over that briefly. There are two levels of access control in gitolite. The first is at the repository level; if you have read (or write) access to *any* ref in the repository, then you have read (or write) access to the repository. The second level, applicable only to "write" access, is by branch or tag within a repository. The username, the access being attempted (`W` or `+`), and the refname being updated are known. The access rules are checked in order of appearance in the config file, looking for a match for this combination (but remember that the refname is regex-matched, not merely string-matched). If a match is found, the push succeeds. A fallthrough results in access being denied. ### Advanced Access Control with "deny" rules ### So far, we've only seen permissions to be one or `R`, `RW`, or `RW+`. However, gitolite allows another permission: `-`, standing for "deny". This gives you a lot more power, at the expense of some complexity, because now fallthrough is not the *only* way for access to be denied, so the *order of the rules now matters*! Let us say, in the situation above, we want engineers to be able to rewind any branch *except* master and integ. Here's how to do that: RW master integ = @engineers - master integ = @engineers RW+ = @engineers Again, you simply follow the rules top down until you hit a match for your access mode, or a deny. Non-rewind push to master or integ is allowed by the first rule. A rewind push to those refs does not match the first rule, drops down to the second, and is therefore denied. Any push (rewind or non-rewind) to refs other than master or integ won't match the first two rules anyway, and the third rule allows it. You can also use deny rules to hide specific repos from people (or gitweb, or git-daemon, etc.), when you have otherwise allowed them access to *all* repos. For example, a server containing open source repos may nevertheless wish to hide the special 'gitolite-admin' repo from gitweb, even though all the other repos can be made visible: repo gitolite-admin - = gitweb daemon [... other access rules ...] config gitolite-options.deny-repo = 1 repo @all R = gitweb daemon See the documentation for more on this. ### Restricting pushes by files changed ### In addition to restricting what branches a user can push changes to, you can also restrict what files they are allowed to touch. For example, perhaps the Makefile (or some other program) is really not supposed to be changed by just anyone, because a lot of things depend on it or would break if the changes are not done *just right*. You can tell gitolite: repo foo RW = @junior_devs @senior_devs RW NAME/ = @senior_devs - NAME/Makefile = @junior_devs RW NAME/ = @junior_devs This powerful feature is documented in `conf/example.conf`. ### Personal Branches ### Gitolite also has a feature called "personal branches" (or rather, "personal branch namespace") that can be very useful in a corporate environment. A lot of code exchange in the git world happens by "please pull" requests. In a corporate environment, however, unauthenticated access is a no-no, and a developer workstation cannot do authentication, so you have to push to the central server and ask someone to pull from there. This would normally cause the same branch name clutter as in a centralised VCS, plus setting up permissions for this becomes a chore for the admin. Gitolite lets you define a "personal" or "scratch" namespace prefix for each developer (for example, `refs/personal//*`); see the "personal branches" section in `doc/3-faq-tips-etc.mkd` for details. ### "Wildcard" repositories ### Gitolite allows you to specify repositories with wildcards (actually perl regexes), like, for example `assignments/s[0-9][0-9]/a[0-9][0-9]`, to pick a random example. This is a *very* powerful feature, which has to be enabled by setting `$GL_WILDREPOS = 1;` in the rc file. It allows you to assign a new permission mode ("C") which allows users to create repositories based on such wild cards, automatically assigns ownership to the specific user who created it, allows him/her to hand out R and RW permissions to other users to collaborate, etc. This feature is documented in `doc/wildcard-repositories.mkd`. ### Other Features ### We'll round off this discussion with a sampling of other features, all of which, and many more, are described in great detail in the "faqs, tips, etc" and other documents. **Logging**: Gitolite logs all successful accesses. If you were somewhat relaxed about giving people rewind permissions (`RW+`) and some kid blew away "master", the log file is a life saver, in terms of easily and quickly finding the SHA that got hosed. **Git outside normal PATH**: One extremely useful convenience feature in gitolite is support for git installed outside the normal `$PATH` (this is more common than you think; some corporate environments or even some hosting providers refuse to install things system-wide and you end up putting them in your own directories). Normally, you are forced to make the *client-side* git aware of this non-standard location of the git binaries in some way. With gitolite, just choose a verbose install and set `$GIT_PATH` in the "rc" files. No client-side changes are required after that :-) **Access rights reporting**: Another convenient feature is what happens when you try and just ssh to the server. Gitolite shows you what repos you have access to, and what that access may be. Here's an example: hello sitaram, the gitolite version here is v1.5.4-19-ga3397d4 the gitolite config gives you the following access: R anu-wsd R entrans R W git-notes R W gitolite R W gitolite-admin R indic_web_input R shreelipi_converter **Delegation**: For really large installations, you can delegate responsibility for groups of repositories to various people and have them manage those pieces independently. This reduces the load on the main admin, and makes him less of a bottleneck. This feature has its own documentation file in the `doc/` directory. **Gitweb support**: Gitolite supports gitweb in several ways. You can specify which repos are visible via gitweb. You can set the "owner" and "description" for gitweb from the gitolite config file. Gitweb has a mechanism for you to implement access control based on HTTP authentication, so you can make it use the "compiled" config file that gitolite produces, which means the same access control rules (for read access) apply for gitweb and gitolite. **Mirroring**: Gitolite can help you maintain multiple mirrors, and switch between them easily if the primary server goes down. [rcdoc]:http://sitaramc.github.com/gitolite/doc/gitolite.rc.html [confdoc]:http://sitaramc.github.com/gitolite/doc/gitolite.conf.html sitaramc-gitolite-6857652/doc/report-output.mkd000066400000000000000000000107651171610012500214610ustar00rootroot00000000000000# F=info_expand output of the "info" and "expand" commands Running "ssh git@server info" or "ssh git@server expand" gives you certain output. This doclet describes the output; you're welcome to help me make it clearer :) ## #info the "info" command Usage: ssh git@server info [optional_pattern [list of users]] The "info" command shows you all the repos (and repo patterns) in the config file that you have been given any kind of access to. If you supply an optional pattern the output will be limited to repos matching that pattern. If you're an admin you can append a list of users to see their permissions instead of your own; in this mode the pattern is mandatory, even if you just use `.` to cheat. Here is a sample output of the info command. There are 3 columns of permissions (create, read, and write) in the output, although the first column is often blank. $ ssh git@server info hello sitaram, this is gitolite v2.1-29-g5a125fa running on git 1.7.4.4 the gitolite config gives you the following access: R SecureBrowse R W anu-wsd R W entrans @R W git-notes @R W gitolite R W gitolite-admin R W indic_web_input @C R W private/sitaram/[\w.-]+ R W proxy @C @R W public/sitaram/[\w.-]+ @R_ @W_ testing R W vkc ### interpreting the output The meaning of C, R, and W are self-explanatory, but they may be prefixed or suffixed by a symbol: * an `@` prefix means "@all" users have been given this permission repo foo R = @all * a `#` prefix means this user is a "superuser" (think root's shell prompt) and so has access to `@all` repos. Which means you'll see this prefix (or, in some cases, an `&`; see next bullet) for *all* the repos, or none of them repo @all R = sitaram * an `&` prefix means both of the above are true The `_` suffix is special. This says the user has only implicit access (due to one of the `@all` uses), but no explicit access. ### #limitoutput using patterns to limit output Here are a couple of samples with optional patterns: $ ssh git@server info git hello sitaram, this is gitolite v2.1-29-g5a125fa running on git 1.7.4.4 the gitolite config gives you the following access: @R W git-notes @R W gitolite R W gitolite-admin $ ssh git@server info admin hello sitaram, this is gitolite v2.1-29-g5a125fa running on git 1.7.4.4 the gitolite config gives you the following access: R W gitolite-admin In "big-config" mode (i.e., when `GL_BIG_CONFIG` is set) the pattern is **mandatory**. You can try and cheat the system by passing in a "." but gitolite truncates the output after 20 results to prevent a DOS. (This limit can be changed; see `$BIG_INFO_CAP` in the documentation for [`~/.gitolite.rc`][rc]). The pattern is also mandatory when an admin wants to find out what access some *other* user has, which you may have guessed from the syntax in the "usage" line above. ### #openssh5.6 side note: openssh 5.6 It used to be that the gitolite documentation would say "just use `ssh git@server`" in the past, because gitolite defaults to the "info" command if no command is passed. However, starting with [openssh 5.6][openssh56], this won't work. The ssh client will now "Kill channel when pty allocation requests fail". This means that gitolite is not even invoked; you only get a message about pty allocation failure, followed by "connection closed". So now you have to use an explicit "info" command, (`ssh git@server info`) or add the `-T` option to ssh (`ssh -T git@server`). [openssh56]: http://www.openssh.org/txt/release-5.6 ## #expand the "expand" command Usage: ssh git@server expand [optional_pattern] The "expand" command trawls through all the repositories on the server, limiting to repos matching the pattern you provide (default is all repos found). For each repo found, it searches for it in the config -- either the actual repo entry (when the repo is not a wildcard repo), or an entry for the wildcard that matches it -- and reports permissions. It also takes into account extra permissions enabled by the `setperms` command (see doc/wildcard-repositories.mkd). It shows you the "creator" of the repo as an additional column, defaulting to `` if it was not a wildcard repo. The limit of number of repos shown in big-config mode (20, by default) described earlier applies to the "expand" command also. sitaramc-gitolite-6857652/doc/ssh-troubleshooting.mkd000066400000000000000000000442201171610012500226230ustar00rootroot00000000000000# F=sts ssh troubleshooting **This document must be read in full the first time. If you start from some nice looking section in the middle it may not help you unless you're already an expert at ssh**. This document should help you troubleshoot ssh-related problems in installing and accessing gitolite. ## IMPORTANT -- READ THIS FIRST ### caveats * Before reading this document, it is **mandatory** to read and **completely understand** [this][gl_ssh], which is a very detailed look at how gitolite uses ssh's features on the server side. Don't assume you know all that; if you knew it, you wouldn't be needing *this* document either! * This document, and others linked from this, together comprise all the help I can give you in terms of the ssh aspect of using gitolite. If you're installing gitolite, you're a "system admin", like it or not. Ssh is therefore a necessary skill. Please take the time to learn at least enough to get passwordless access working. * Please note that authentication is not really gitolite's job at all. I'd rather spend time on actual gitolite features, code, and documentation than authentication (i.e., ssh, in the common case). Surprised? [This][auth] might help explain better. ### naming conventions used * Your workstation is the **client**. Your userid on the client does not matter, and it has no relation to your gitolite username. * the server is called **server** and the "hosting user" is **git**. If this is an RPM/DEB install, the hosting user is probably called "gitolite", however we will use "git" in this document. ### taking stock -- relevant files and directories * the client has a `~/.ssh` containing a few keypairs. It may also have a `config` file. * the client also has a clone of the "gitolite-admin" repo, which contains a bunch of `*.pub` files in `keydir`. We assume this clone is in `$HOME`; if it is not, adjust instructions accordingly when needed. * The git user on the server has a `~/.ssh/authorized_keys` file that the ssh daemon uses to authenticate incoming users. We often call this file **authkeys** to save typing, and it always means the one on the server (we're not interested in this file on the client side). * the server also has a `~/.gitolite/keydir` which contains a bunch of `*.pub` files. ### normal gitolite key handling Here's how normal gitolite key handling works: * (on client) pub key changes like adding new ones, deleting old ones, etc., are done in the `keydir` directory in the gitolite-admin repo clone. Then the admin `git add`s and `git commit`s those changes, then `git push`es them to the server. * (on server) a successful push from the client makes git invoke the post-update hook in the gitolite-admin repo. This hook is installed by gitolite, and it does a bunch of things which are quite transparent to the admin, but we'll describe briefly here: * the pubkey files from this push are checked-out into `~/.gitolite/keydir` (and similarly the config files into `~/.gitolite/conf`) * the "compile" script then runs, which uses these files to populate `~/.ssh/authorized_keys` on the server The authkeys file may have other, (non-gitolite) keys also. Those lines are preserved. Gitolite only touches lines that are found between gitolite's "marker" lines (`# gitolite start` and `# gitolite end`). ## (Other resources) People who think installing gitolite is too hard should take a look at this [tutorial][tut] to **see how simple it *actually* is**. ## common ssh problems Since I'm pretty sure at least some of you didn't bother to read the "IMPORTANT: PLEASE READ FIRST" section above, let me take a minute to point you there again. Especially the first bullet. Done? OK, read on... The following problem(s) indicate that pubkey access is not working at all, so you should start with [appendix 1][stsapp1_]. If that doesn't fix the problem, continue with the other appendices in sequence. * running any git clone/fetch/ls-remote or just `ssh git@server info` asks you for a password. The following problem(s) indicate that your pubkey is bypassing gitolite and going straight to a shell. You should start with [appendix 2][stsapp2_] and continue with the rest in sequence. [Appendix 5][stsapp5_] has some background info. * running `ssh git@server info` gets you the output of the GNU 'info' command instead of gitolite's version and access info. * running `git clone git@server:repositories/reponame` (note presence of `repositories/` in URL) works. [A proper gitolite key will only let you `git clone git@server:reponame` (note absence of `repositories/`)] * you are able to clone repositories but are unable to push changes back (the error complains about the `GL_RC` environment variable not being set, and the `hooks/update` failing in some way). [If you run `git remote -v` you will find that your clone URL included the `repositories/` described above!] * conversely, using the correct syntax, `git clone git@server:reponame` (note absence of `repositories/` in the URL), gets you `fatal: 'reponame' does not appear to be a git repository`, and yet you are sure 'reponame' exists, you haven't mis-spelled it, etc. ## step by step Since I'm pretty sure at least some of you didn't bother to read the "IMPORTANT: PLEASE READ FIRST" section above, let me take a minute to point you there again. Especially the first bullet. Done? OK, now the general outline for ssh troubleshooting is this: * make sure the server's overall setup even *allows* pubkey based login. I.e., check that git fetch/clone/ls-remote commands or a plain `ssh git@server info` do NOT ask for a password. If you do get asked for a password, see [appendix 1][stsapp1_]. * match client-side pubkeys (`~/.ssh/*.pub`) with the server's authkeys file. To do this, run `sshkeys-lint`, which tells you in detail what key has what access. See [appendix 2][stsapp2_]. * at this point, we know that we have the right key, and that if sshd receives that key, things will work. But we're not done yet. We still need to make sure that this specific key is being offered/sent by the client, instead of the default key. See [appendix 3][stsapp3_] and [appendix 4][sshhostaliases]. ## random tips, tricks, and notes ### giving shell access to gitolite users We've managed (thanks to an idea from Jesse Keating) to make it possible for a single key to allow both gitolite access *and* shell access. This is done by copying the pubkey (to which you want to give shell access) to the server and running gl-tool add-shell-user ~/foo.pub **IMPORTANT UPGRADE NOTE**: previous implementations of this feature were crap. There was no easy/elegant way to ensure that someone who had repo admin access would not manage to get himself shell access. Giving someone shell access requires that you should have shell access in the first place, so the simplest way is to enable it from the server side only. ### losing your admin key If you lost the admin key, and need to re-establish ownership of the gitolite-admin repository with a fresh key, get a shell on the server and use the program called `gl-admin-push` that comes with gitolite. See instructions [here][adminpush]. ### simulating ssh-copy-id don't have `ssh-copy-id`? This is broadly what that command does, if you want to replicate it manually. The input is your pubkey, typically `~/.ssh/id_rsa.pub` from your client/workstation. * it copies it to the server as some file * it appends that file to `~/.ssh/authorized_keys` on the server (creating it if it doesn't already exist) * it then makes sure that all these files/directories have go-w perms set (assuming user is "git"): /home/git/.ssh/authorized_keys /home/git/.ssh /home/git [Actually, `sshd` requires that even directories *above* `~` (`/`, `/home`, typically) also must be `go-w`, but that needs root. And typically they're already set that way anyway. (Or if they're not, you've got bigger problems than gitolite install not working!)] ### problems with using non-openssh public keys Gitolite accepts public keys only in openssh format. Trying to use an "ssh2" key (used by proprietary SSH software) results in: WARNING: a pubkey file can only have one line (key); ignoring YourName.pub To convert ssh2-compatible keys to openssh run: ssh-keygen -i -f /tmp/ssh2/YourName.pub > /tmp/openssh/YourName.pub then use the resulting pubkey as you normally would in gitolite. ### windows issues On windows, I have only used msysgit, and the openssh that comes with it. Over time, I have grown to distrust putty/plink due to the number of people who seem to have trouble when those beasts are involved (I myself have never used them for any kind of git access). If you have unusual ssh problems that just don't seem to have any explanation, try removing all traces of putty/plink, including environment variables, etc., and then try again. Thankfully, someone contributed [contrib/putty.mkd][contrib_putty]. ## #stsapp1_ appendix 1: ssh daemon asks for a password > **NOTE**: This section should be useful to anyone trying to get > password-less access working. It is not necessarily specific to gitolite, > so keep that in mind if the wording feels a little more general than you > were expecting. You have generated a keypair on your workstation (`ssh-keygen`) and copied the public part of it (`~/.ssh/id_rsa.pub`, by default) to the server. On the server you have appended this file to `~/.ssh/authorized_keys`. Or you ran something, like the `gl-setup` step during a gitolite install, which should have done that for you. You now expect to log in without having to type in a password, but when you try, you are being asked for a password. This is a quick checklist: * make sure you're being asked for a password and not a pass*phrase*. Do not confuse or mistake a prompt saying `Enter passphrase for key '/home/sitaram/.ssh/id_rsa':` for a password prompt from the remote server! When you create an ssh keypair using `ssh-keygen`, you have the option of protecting it with a passphrase. When you subsequently use that keypair to access a remote host, your *local* ssh client needs to unlock the corresponding private key, and ssh will probably ask for the passphrase you set when you created the keypair. You have two choices to avoid this prompt every time you try to use the private key. The first is to create keypairs *without* a passphrase (just hit enter when prompted for one). **Be sure to add a passphrase later, once everything is working, using `ssh-keygen -p`**. The second is to use `ssh-agent` (or `keychain`, which in turn uses `ssh-agent`) or something like that to manage your keys. Other than discussing one more potential trouble-spot with ssh-agent (see below), further discussion of ssh-agent/keychain is out of scope of this document. * ssh is very sensitive to permissions. An extremely conservative setup is given below, but be sure to do this on **both the client and the server**: cd $HOME chmod go-rwx . chmod -R go-rwx .ssh * actually, every component of the path to `~/.ssh/authorized_keys` all the way upto the root directory must be at least `chmod go-w`. So be sure to check `/` and `/home` also. * while you're doing this, make sure the owner and group info for each of these components are correct. `ls -ald ~ ~/.ssh ~/.ssh/authorized_keys` will tell you what they are. * you may also want to check `/etc/ssh/sshd_config` to see if the "git" user is allowed to login at all. For example, if that file contains an `AllowUsers` config entry, then only users mentioned in that line are allowed to log in! * some OSs/distributions require that the "git" user should have a password and/or not be a locked account. You may want to check that as well. * if all that fails, log onto the server as root, `cd /var/log`, and look for a file called `auth.log` or `secure` or some such name. Look inside this file for messages matching the approximate time of your last attempt to login, to see if they tell you what is the problem. ## #stsapp2_ appendix 2: which key is which -- running sshkeys-lint Follow these steps on the client: * get a copy of `~/.ssh/authorized_keys` from the server and put it in `/tmp/foo` or something * cd to `~/.ssh` * run `sshkeys-lint *.pub < /tmp/foo` This tells you, for each pubkey, what type of access (if any) it has to the server. Note that it is not trying to log in or anything -- it's just comparing bits of text (the contents of STDIN taken as an authkeys file, and the contents of each of the `*.pub` files one by one). > Note: It's also a stand-alone program, so even if your gitolite version is > old, you can safely bring over just this program from a more recent > gitolite and use it, without having to upgrade gitolite itself. If the pubkey file you're interested in appears to have the correct access to the server, you're done with this step. Otherwise you have to rename some keypairs and try again to get the effect you need. Be careful: * do not just rename the ".pub" file; you will have to rename the corresponding private key also (the one with the same basename but without an extension) * if you're running ssh-agent, you may have to delete (using `ssh-add -D`) and re-add identities for it to pick up the renamed ones correctly ### typical cause(s) The admin often has passwordless shell access to `git@server` already, and then used that same key to get access to gitolite (i.e., copied that same pubkey as YourName.pub and ran `gl-setup` on it). As a result, the same key appears twice in the authkeys file now, and since the ssh server will always use the first match, the second occurrence (which invokes gitolite) is ignored. To fix this, you have to use a different keypair for gitolite access. The best way to do this is to create a new keypair, copy the pubkey to the server as YourName.pub, then run `gl-setup YourName.pub` on the server. Remember to adjust your agent identities using ssh-add -D and ssh-add if you're using ssh-agent, otherwise these new keys may not work. ## #stsapp3_ appendix 3: ssh client may not be offering the right key * make sure the right private key is being offered. Run ssh in very verbose mode and look for the word "Offering", like so: ssh -vvv user@host pwd 2> >(grep -i offer) If some keys *are* being offered, but not the key that was supposed to be used, you may be using ssh-agent (next bullet). You may also need to create some host aliases in `~/.ssh/config` ([appendix 4][sshhostaliases]). * (ssh-agent issues) If `ssh-add -l` responds with either "The agent has no identities." or "Could not open a connection to your authentication agent.", then you can skip this bullet. However, if `ssh-add -l` lists *any* keys at all, then something weird happens. Due to a quirk in ssh-agent, ssh will now *only* use one of those keys, *even if you explicitly ask* for some other key to be used. In that case, add the key you want using `ssh-add ~/.ssh/YourName` and try the access again. ## F=sshhostaliases appendix 4: host aliases (or "making git use the right options for ssh") The ssh command has several options for non-default items to be specified. Two common examples are `-p` for the port number if it is not 22, and `-i` for the public key file if you do not want to use just `~/.ssh/id_rsa` or such. Git has two ssh-based URL syntaxes, but neither allows specifying a non-default public key file. And a port number is only allowed in one of them. (See `man git-clone` for details). Finally, hosts often have to be referred with IP addresses (such is life), or the name is very long, or hard to remember. Using a "host" para in `~/.ssh/config` lets you nicely encapsulate all this within ssh and give it a short, easy-to-remember, name. Example: host gitolite user git hostname a.long.server.name.or.annoying.IP.address port 22 identityfile ~/.ssh/id_rsa Now you can simply use the one word `gitolite` (which is the host alias we defined here) and ssh will infer all those details defined under it -- just say `ssh gitolite` and `git clone gitolite:reponame` and things will work. (By the way, the 'port' and 'identityfile' lines are needed only if you have non-default values, although I put them in anyway just to be complete). If you have *more than one* pubkey with access to the *same* server, you **must** use this method to make git pick up the right key. There is no other way to do this, as far as I know. [tut]: http://sites.google.com/site/senawario/home/gitolite-tutorial ## #stsapp5_ appendix 5: why bypassing gitolite causes a problem When you bypass gitolite, you end up running your normal shell instead of the special gitolite entry point script `gl-auth-command`. This means commands (like 'info') are interpreted by the shell instead of gitolite. It also means git operations look for repos in `$HOME`. However, gitolite places all your repos in a subdirectory pointed to by `$REPO_BASE` in the rc file (default: `repositories`), and internally prefixes this before calling the actual git command you invoked. Thus, the pathname of the repo that you use on the client is almost never the correct pathname on the server. (This is by design. Don't argue...) This means that, you get 2 kinds of errors if you bypass gitolite * when you use `git@server:reponame` with a key that bypasses gitolite (i.e., gets you a shell), this prefixing does not happen, and so the repo is not found. Neither a clone/fetch nor a push will work. * conversely, consider `git@server:repositories/reponame.git`. The clone operation will work -- you're using the full Unix path, (assuming default `$REPO_BASE` setting), and so the shell finds the repo where you said it would be. However, when you push, gitolite's **update hook** kicks in, and fails to run because some of the environment variables it is expecting are not present. sitaramc-gitolite-6857652/doc/support.mkd000066400000000000000000000027701171610012500203210ustar00rootroot00000000000000# F=support support for gitolite Email addresses etc are in the "contact and license" section at the bottom of the [main](http://sitaramc.github.com/gitolite/) document. If you're willing to read, you will only need support for the most obscure issues. I often don't bother responding for silly issues that are already in the docs. Seriously, folks, troubleshooting involves reading; do I really need to *say* that? * Lost your admin key? Try [gl-admin-push][adminpush]. * For install/setup issues see the [install trouble?][insttrouble] section of the [install][] document. In particular, that section has two links for ssh issues. **I've yet to see an ssh problem that is not already covered by those** (but please tell me if you find one!) * The [suggested reading list][rtfm] section is a quick overview of the documentation. * If you still can't find what you need, try the [**master index/table of contents**](http://sitaramc.github.com/gitolite/master-toc.html) and use your browser's search function. ---- **Note on the online docs**: the online docs break up longer documents by putting less "central" concepts into subdocuments (replacing the text with a link so you know it's there). I did this because the most frequent complaint was "TL;DR" (your documentation is too long); I do believe it is now easier to find things/navigate. Of course, now someone complained that it took him "4 middle-of-text clicks" to reach some section... sigh! sitaramc-gitolite-6857652/doc/tips-notes.mkd000066400000000000000000000411621171610012500207100ustar00rootroot00000000000000# F=tips assorted tips and notes ## common errors and mistakes * adding `repositories/` at the start of the repo name in the `git clone`. This error is typically made by the *admin* himself -- because he knows what `$REPO_BASE` is set to and thinks he has to provide that prefix on the client side also :-) In fact gitolite prepends `$REPO_BASE` internally, so you shouldn't also do the same thing! * being able to clone but getting errors on push. Most likely caused by a combination of: * you already have shell access to the server, not just "gitolite" access, *and* * you cloned using `git clone git@server:repositories/repo.git` (notice there's an extra "repositories/" in there?) In other words, you used a key that completely bypassed gitolite and went straight to the shell to do the clone. Please see doc/ssh-troubleshooting.mkd for what all this means. ## other errors, warnings, notes... ### cloning an empty repo Cloning an empty repo is only possible with clients greater than 1.6.2. So at least one of your clients needs to have a recent git. Once at least one commit has been made, older clients can also use it When you clone an empty repo, git seems to complain about `fatal: The remote end hung up unexpectedly`. However, you can ignore this, since it doesn't seem to hurt anything. [Update 2009-09-14; this has been fixed in git 1.6.4.3] ### `@all` syntax for repos There *is* a way to use the `@all` syntax for repos also, as described in `doc/gitolite.conf.mkd`. However, there are a couple of minor cautions: * don't use `NAME/` or such restrictions on the special `@all` repo. Due to the potential for defeating a crucial optimisation and slowing down *all* access, we do not support this. ## features Apart from the big ones listed in the top level README, and subjective ones like "better config file format", gitolite has evolved to have many useful features than the original goal of branch-level access control. ### syntax and normal usage #### #multikey one user, many keys If you have a user who has more than one pubkey (like from different machines) the simplest way to deal with it is to add subdirectories and add keys there. For example, I might have these files in `keydir/`: sitaram.pub home/sitaram.pub laptop/sitaram.pub ##### F=oldmultikeys old style multi keys This is an older method of enabling multi-keys. It will continue to work and be supported in *code*, simply because I prefer it. But I am not going to document it except for the example below, nor am I going to support it in terms of questions. Sorry. Apparently it was too complex to understand, even for some smart folks I know. This tells me it was probably ill thought out and should have been obsoleted as soon as e0fe73a was pushed. Anyway, here's *all* the documentation for it -- some sample pubkey filenames and the corresponding derived usernames: * plain username, no multikey sitaramc.pub sitaramc * plain username, with multikeys sitaramc@laptop.pub sitaramc sitaramc@desktop.pub sitaramc * email address as username, no multikey sitaramc@gmail.com.pub sitaramc@gmail.com * email address as username, with multikeys sitaramc@gmail.com@laptop.pub sitaramc@gmail.com sitaramc@gmail.com@desktop.pub sitaramc@gmail.com ### F=tipssec_ security, access control, and auditing #### #2levels two levels of access rights checking Gitolite has two levels of access checks. The **first check** is what I will call the **pre-git** level. At this stage, the `gl-auth-command` has been invoked by `sshd`, and it knows just three things: * who, * what repository, and * what type of access (R or W) Note that at this point no git program has entered the picture, and we have no way of knowing what **ref** (branch, tag, etc) he is trying to update, even if it is a "write" operation. For a "read" operation to pass this check, the username (or `@all` users) must have read permission (i.e., R, RW, RW+, etc.) on at least one branch of the repo (or `@all` repos). For a "write" operation, there is an additional restriction: lines specifying only `R` (read access) don't count. *The user must have write access to **some** ref in the repo in order to pass this stage!* The **second check** is via a git `update hook`. This check only happens for write operations. By this time we know what "ref" he is trying to update, as well as the old and the new SHAs of that ref (by which we can also deduce whether it's a rewind or not). This is where the "per-branch" permissions come into play. Each refex that allows `W` access (or `+` if this is a rewind) for *this* user, on *this* repo, is matched against the actual refname being updated. If any of the refexes match, the push succeeds. If none of them match, it fails. Gitolite also allows "exclude" or "deny" rules. See later in this document for details. #### better logging If you have been too liberal with the permission to rewind, it has built-in logging as an emergency fallback if someone goes too far, or for audit purposes [`*`]. The logfile names and location are configurable, and can include the year/month/day etc in the filename for easy archival or further processing. The log file even tells you which pattern in the config file matched to allow that specific access to proceed. > [`*`] setting `core.logAllRefUpdates true` does provide a safety net > against over-zealous rewinds, but it does not tell you "who". And > strangely, management does not seem to share the view that "blame" is just > a synonym for "annotate" ;-)] The log lines look like this: 2009-09-19.10:24:37 + b4e76569659939 4fb16f2a88d8b5 myrepo refs/heads/master user2 refs/heads/master The "+" at the start indicates a non-fast forward update, in this case from b4e76569659939 to 4fb16f2a88d8b5. So b4e76569659939 is the one to restore! Can it get easier? The other parts of the log line are the name of the repo, the refname being updated, the user updating it, and the refex pattern (from the config file) that matched, in case you need to debug the config file itself. #### delegating parts of the config file You can now split up the config file and delegate the authority to specify access control for their own pieces. See [delegation][deleg] for details. ### F=tnconv_ convenience features #### what repos do I have access to? Sometimes there are too many repos, maybe even named similarly, or with the potential for typos, confusion about hyphens/underscores or upper/lower case, etc. You'd just like a simple way to know what repos you have access to. Gitolite provides two commands ([`info`][info] and [`expand`][expand]) to help you find this information. #### support for git installed outside default PATH The normal solution is to add to the system default PATH somehow, either by munging `/etc/profile` or by enabling `PermitUserEnvironment` in `/etc/ssh/sshd_config` and then setting the PATH in `~/.ssh/.environment`. All these are security risks because they allow a lot more than just you and your git install :-) And if you don't have root, you can't do this anyway. The only solution till now has been to ask every client to set the config parameters `remote..receivepack` and `remote..uploadpack`. But telling *every* client to do so is a pain... Gitolite lets you specify the directory in which git binaries are to be found, via a new variable (`$GIT_PATH`) in the "rc" file. If this variable is non-empty, it will be appended to the PATH environment variable before attempting to run git stuff. Very easy, very simple, and completely transparent to the users :-) **Note**: sometimes you have a system that already has an older "git" installed in one of the system PATHs, but you've installed a newer git in some non-standard location and want that picked up. Because of security reasons, gitolite will not prepend `GIT_PATH` to the PATH variable, so the older git comes first and it gets kinda frustrating! Here's a simple workaround. Ignore the `GIT_PATH` variable, and directly set the full PATH in the rc file, like so: $ENV{PATH} = "/home/sitaram/bin:$ENV{PATH}"; #### #pers "personal" branches "personal" branches are great for corporate environments, where unauthenticated pull/clone is a no-no. Since a dev workstation cannot do authentication, even work shared just between 2 devs has to go *via* the server. This causes the same branch name clutter as in a centralised VCS, plus setting up permissions for this becomes a chore for the admin. Personal branches exist **in a namespace** of their own. The syntax is RW+ personal/USER/ = @userlist where the "personal" can be anything you like (but cannot be empty), and the "/USER/" part is **necessary (including both slashes)**. A user "alice" (if she's in the userlist) can then push any branches inside `personal/alice/`. Which means she can push `personal/alice/foo` and `personal/alice/bar`, but NOT `personal/alice`. (Background: at runtime the "USER" component will be replaced by the name of the invoking user. Access is determined by the right hand side, as usual). #### custom hooks and custom git config You can specify hooks that you want to propagate to all repos, as well as per-repo "gitconfig" settings. Please see `doc/2-admin.mkd` and `doc/gitolite.conf.mkd` for details. #### bypassing gitolite Sometimes you'll need to access one of the gitolite-managed repos directly on the server, without going through gitolite. Reasons may be some automatic updates or some other ad hoc purposes you can dream up. Cloning a gitolite-controlled repo is easy enough -- just use the full path (typically `~/repositories/reponame.git`) instead of just `reponame`, to compensate for gitolite not sitting in between and adding those things to the repo path. But when you push, the update hook (which git will invoke anyway) will fail because it needs all sorts of access control info that it now doesn't have, because the push was invoked without going through gitolite. In order to bypass the update hook, just set the `GL_BYPASS_UPDATE_HOOK` environment variable to "1" or something, export it, and push. I prefer not to set that variable permanently, preferring this mode instead: GL_BYPASS_UPDATE_HOOK=1 git push #### F=adminpush gl-admin-push: bypassing gitolite for the gitolite-admin repo The method described in the previous section (setting `GL_BYPASS_UPDATE_HOOK`) will work for all the repos managed by gitolite, **except** for the special `gitolite-admin` repo. For that you will need some extra magic, because there is also a `post-update` hook that runs here, and this needs additional information which is NOT available if you bypass gitolite. (Note: If your gitolite is too old to have the `gl-admin-push` program, try `gl-dont-panic`; run it without arguments for usage info. If you don't even have that, it may be best to [clean][] things out more thoroughly!) Use the `gl-admin-push` program to make changes to the admin repo *directly on the server*. Here's how: * clone the repo to some safe location and cd to it: cd /tmp git clone ~/repositories/gitolite-admin.git cd gitolite-admin * make whatever changes you want to that clone and commit. You can add new keys, change the conf file, or anything at all that needs fixing up. You can even reset to an older commit (rewind) if that is the simplest way to fix up some config problem that may have lost you your access. * when done, instead of `git push `, use this program instead. For example, instead of `git push -f`, use `gl-admin-push -f`. Note that this method will work for *any* repo, not just the special admin repo. #### #disable disabling write access to take backups If you want to take normal, OS-level, backups of the system, you might want git to be quiescent during that time, so that the backup is clean. The best way to do this is to disable write-access to the server for the duration of the backup. Here's how: cd $HOME # if running as "git" user, else "cd ~git" or whatever echo writes disabled during backup window > .gitolite.down # << RUN YOUR BACKUP COMMAND(s) HERE >> rm .gitolite.down I leave it to you to * make sure that if the backup script fails, the `.gitolite.down` file is still removed (or not; maybe your policy is that if the backup failed, no further writes are allowed. Whatever...) * if you're extremely paranoid (even I wouldn't worry about this!) make sure that no push is *in progress* by checking for any `git-receive-pack` processes in a `ps` output. ### INconvenience features #### #repodel deleting a repo By design, there is no code in gitolite to *delete* a repo if the repo was specified by name in the config file. (Wildcard repos *can* be deleted by the user; see [here][wild_repodel] for details). If you *do* want to permanently delete a *non*-wildcard repo, here's what you do: * remove the repo from the gitolite-admin repo clone's `conf/gitolite.conf` file. "add" the change, commit, and push. * *then* remove the repo from `~/repositories` on the server (or whatever you set `$REPO_BASE` to in the `~/.gitolite.rc`) #### renaming a repo This is similar; there's no code to do this in gitolite. What you do is: * log on to the server, `cd $REPO_BASE` (default: `cd ~/repositories`), and `mv old-name.git new-name.git` * back on your gitolite-admin clone, edit `conf/gitolite.conf` and replace all occurrences of `old-name` with `new-name`. Then add, commit, and push as usual. The order of these 2 steps is important; do not reverse them :-) ### helping with gitweb Although gitweb is a completely separate program, gitolite can do quite a lot to help you manage gitweb access as well; once the initial setup is complete, you can do it all from within the gitolite config file! If you just want gitweb to show some repositories, see [here][gwd] for how to specify which repos to show. #### #gitwebauth easier to link gitweb authorisation with gitolite Over and above whether a repo is even *shown* by gitweb, you may want to further restrict people, allowing them to view *only* those repos for which they have been given read access by gitolite. This requires that: * you have to have some sort of HTTP auth on your web server (out of my scope, sorry!) * the HTTP auth should use the same username (like "sitaram") as used in the gitolite config (for the corresponding user) Normally a superuser sets up passwords for users using the "htpasswd" command, but this is an administrative chore. Robin Smidsrød had the *great* idea that, since each user already has pubkey access to `git@server`, this gives us a very neat way of using gitolite to let the users *manage their own HTTP passwords*. Here's how: * setup apache so that the htaccess file it looks for is owned by the "git" user * in the `~/.gitolite.rc` file, look for the variable `$HTPASSWD_FILE` and point it to this file * tell your users to type in `ssh git@server htpasswd` to set or change their HTTP passwords Of course some other authentication method can be used (e.g. `mod_ldap`) as long as the usernames match. Gitweb allows you to specify a subroutine to decide on access. We use that feature and tie it to gitolite. Configuration example can be found in `contrib/gitweb/`. #### #umask umask setting Gitweb not able to read your repos? You can change the umask for newly created repos to something more relaxed -- see the `REPO_UMASK` setting in the [rc file documentation][rc]. ### advanced features There are some really cool features that are now in pretty wide use. * **[repos named with wildcards][wild]** is useful when some or most of your repos fit a pattern, avoiding the need to name repos individually in the config file. New repos matching the pattern can be created by any user (if you give them rights to), with a set of permissions assigned to "roles", and the creator can then place users into those roles. * **[admin defined commands][ADCs]** allow controlled access to specific commands and scripts without giving users full shell access. ### odds and ends #### "poking" the admin repo to force a compile Sometimes you need to force a compile, as if you pushed the gitolite-admin repo. I have a git alias that looks like this: [alias] poke = !git ls-remote origin | grep -w refs/heads/poke && git push origin :poke || git push origin master:poke so I just run `git poke`. This toggles between deleting and creating a dummy branch called "poke". Either operation will trigger the hooks. sitaramc-gitolite-6857652/doc/user.mkd000066400000000000000000000120731171610012500175600ustar00rootroot00000000000000# F=user what users (not admins) need to know about gitolite ...written for the one guy in the world no one will think of as "just a normal user" ;-) This document has some text, and a lot of links. Most of this info *is* available in the rest of the documentation, but it's scattered and sparse. Collecting all of it, or at least links to it, in one place sounds useful. ## accessing gitolite The most common setup is based on ssh, where your admin asks you to send him your public key, and uses that to setup your access. Your actual access is either a git command (like `git clone git@server:reponame`, and we won't be discussing these any more in this document), or an ssh command (like `ssh git@server info`). Note that you do *not* get a shell on the server -- the whole point of gitolite is to prevent that! ## always available commands The only command that is *always* available to every user is the [`info` command][info], which tells you what version of gitolite and git are on the server, and what repositories you have access to. The list of repos is very useful if you have doubts about the spelling of some new repo that you know was setup. ## digression: two kinds of repos Gitolite has two kinds of repos. Normal repos are specified by their full names in the config file. "Wildcard" repos are specified by a regex in the config file. Try the [`info` command][info] and see if it shows any lines that look like regex patterns, (with a "C" permission in addition to the "R" and the "W"). If you see any, it means you are allowed to create brand new repos whose names fit that pattern. When you create such a repo, your "ownership" of it (as far as gitolite is concerned) is *automatically* recorded by creating a 1-line file called "gl-creater" (note spelling!) in the repo directory, with just your gitolite userid in it. This is for new repos you create. But for repos that already existed and were migrated into gitolite by the admin, the admin has to manually create that "gl-creater" file, otherwise you won't be able to execute certain commands that you otherwise might have access to. "Wildrepos" is an optional feature of gitolite that the admin has to explicitly enable. ## commands only available with "wildrepos" on ### listing repos you created The info command will not show you your own wildcard repos. To get that list, try the [`expand` command][expand]. ### set/get additional permissions for repos you created The gitolite config may have several permissions lines for your repo, like so: repo pub/CREATOR/..* RW+ = CREATOR RW = user1 user2 R = user3 If that's all it had, you really can't do much. Any changes to access must be done by the administrator. (Note that "CREATOR" is a reserved word that gets expanded to your userid in some way, so the admin can literally add just the first two lines, and *every* authenticated user now has his own personal repo namespace, starting with `pub//`). To give some flexibility to users, the admin could add rules like this: RW = WRITERS R = READERS (he could also add other roles but then he needs to read the documentation). Once he does this, you can then use the [`setperms` command][setperms] to set permissions for other users by specifying which users are in the list of "READERS", and which in "WRITERS". That same section also talks about the `getperms` command as well. If you think of READERS and WRITERS as "roles", it will help. You can't change what access a role has, but you *can* say which users have that role. **Note**: there isn't a way for you to see the actual rule set unless you're given read access to the special 'gitolite-admin' repo. Sorry. The idea is that your admin will tell you what "roles" he added into rules for your repos, and what permissions those roles have. ### #setdesc adding a description to repos you created The `setdesc` and `getdesc` commands work similarly to the `setperms` and `getperms` commands. You just say echo "some description here" | ssh git@server setdesc pub// and if you want to check you just say ssh git@server getdesc pub// ## "site-local" commands The main purpose of gitolite is to prevent you from getting a shell. But there are commands that you often need to run on the server (i.e., cannot be done by pushing something to a repo). To enable this, gitolite allows the admin to setup scripts in a special directory that users can then run. Gitolite comes with a set of working scripts (in "contrib/adc") that your admin may install, or may use as a starting point for his own, if he chooses. Think of these commands as equivalent to those in `COMMAND_DIR` in `man git-shell`. Most of the shipped ADCs are briefly described [here][shipped_ADCs], with links to more details if available. However, **please understand** that these commands may not be available, or their behaviour may have been changed to suit local requirements, and of course new ones may have been added. You'll have to ask your local admin for answers, not me! sitaramc-gitolite-6857652/doc/who-uses-it.mkd000066400000000000000000000075401171610012500207710ustar00rootroot00000000000000# F=who who uses gitolite > > If you're using gitolite and find it very useful in some way, I would > > love to describe your use of it or add a link to your own description > > of it here. Of course, you can anonymise it as much as you need to. The **Fedora Project** controls access to over 10,000 package management repositories accessed by over 1,000 package maintainers [using gitolite][fedora]. This is probably the largest *confirmed* gitolite installation anywhere. The whole [big-config][bc] thing was initially done for them (their config file was so big that without the big-config changes gitolite would just run out of memory and die!). [fedora]: http://lists.fedoraproject.org/pipermail/devel-announce/2010-July/000647.html The **KDE project** [uses][kde] gitolite (in combination with redmine for issue tracking and reviewboard for code review). Apart from the usual access control, the KDE folks are heavy users of the "ad hoc repo creation" features enabled by wildrepos and the accompanying commands. Several of the changes to the "admin defined commands" were also inspired by KDE's needs. See [section 5][s5] and [section 6][s6] of the above linked page for details. [kde]: http://community.kde.org/Sysadmin/GitKdeOrgManual [s5]: http://community.kde.org/Sysadmin/GitKdeOrgManual#Server-side_commands [s6]: http://community.kde.org/Sysadmin/GitKdeOrgManual#Personal_repositories [kdera]: http://permalink.gmane.org/gmane.comp.kde.scm-interest/1437 **Prof. Hiren Patel** of the University of Waterloo is responsible for the existence of the fairly popular "[wildrepos][wild]" feature. The documentation was pretty much written with his use case in mind, but of course it turns out to be useful for a lot of people, as you can see from the previous para on KDE's use of gitolite. In fact, he surprised the heck out of me recently by saying that if it hadn't been for this feature, he might not have used git itself -- which is a pretty serious compliment if you think about the magnitude of the git project and my little one-man show! He explains his use of it [here][hiren]. [hiren]: http://ece.uwaterloo.ca/~hdpatel/uwhtml/wildrepos-in-gitolite/ **Gentoo Linux** has [just moved][gentoo1] their git repositories from gitosis to gitolite. There are about 200 repositories, some of them are the so called [overlays][gentoo2], official and unofficial/user overlays, plus several developer and project repositories, used by more than 1000 people. That number will be increased in the near future, as they are going to migrate some of their CVS/SVN repositories there, plus they are offering overlays hosting for users as well. [gentoo1]: http://archives.gentoo.org/gentoo-dev/msg_2812c9b9e768f64b46360ab17b9d0024.xml [gentoo2]: http://www.gentoo.org/proj/en/overlays/ **Nokia MeeGo** uses Gitolite internally, and has also contributed LDAP specific code (see [contrib/ldap][ldap] directory for details). [ldap]: http://github.com/sitaramc/gitolite/blob/pu/contrib/ldap **kernel.org**, the official distribution point for the Linux kernel, is the latest (as of 2011-10) high-visibility installation. According to [this email][ko-ann] to the lkml, kernel.org decided to use gitolite for access controlling their git repos. This move also prompted the first ever security audit of gitolite by an outside party. Gitolite did great; see [here][audit] for details. In addition, kernel.org was responsible for a serious rethink of a few rough edges in gitolite, and smoothing them out was fun (the "playing with gitolite" stuff, making the test suite simpler, "deny" rules for the entire repo). [ko-ann]: http://lkml.org/lkml/2011/9/23/357 [audit]: http://groups.google.com/group/gitolite/browse_thread/thread/8dc5242052b16d0f ---- A general note: if you see the list of high-profile users above, you will see that gitolite benefits as much as they do; possibly more. color me happy... sitaramc-gitolite-6857652/doc/wildcard-repositories.mkd000066400000000000000000000257271171610012500231320ustar00rootroot00000000000000# F=wild repositories named with wildcards ***IMPORTANT NOTE***: This feature may be somewhat "brittle" in terms of security. Creating repositories based on wild cards, giving "ownership" to the specific user who created it, allowing him/her to hand out permissions to other users to collaborate, all these are possible. And any of these could have a bug in it. I haven't found any yet, but that doesn't mean there aren't any. ---- This document is mostly "by example". ---- ## quick introduction The wildrepos feature allows you to specify access control rules using regular expression patterns, so you can have many actual repos being served by a single set of rules in the config file. The regex pattern can also include the word `CREATOR` in it, allowing you to parametrise the name of the user creating the repo. The examples below will make this clearer. ## rc file setting required This feature requires that you set `$GL_WILDREPOS` to "1" in `~/.gitolite.rc` on the server. Please search for that variable in `doc/gitolite.rc.mkd` for more information on this. ## examples of wildcard repos As the introduction said, you can include the word `CREATOR` in the regex pattern, though it is not mandatory. We'll look at examples of both types of usage. Which of these alternatives you choose depends on your needs, and the social aspects of your environment. Including the creator name in the pattern keeps users rigidly separated from each others repos, and is good for a largely autonomous collection of users with a high probability of repo name clashes. Omitting the creator name from the pattern puts the repos in a common namespace, and is suitable for environments where it is not very important to keep track of who actually created the repo (except for granting access), but needs more communication / co-operation among the users to avoid repo name clashes. ### wildcard repos with creator name in them Here's an example snippet: @prof = u1 @TAs = u2 u3 @students = u4 u5 u6 repo assignments/CREATOR/a[0-9][0-9] C = @students RW+ = CREATOR RW = WRITERS @TAs R = READERS @prof Note the "C" permission. This is a standalone "C", which gives the named users the right to *create a repo*. This is not to be confused with the "RWC" or its variants described elsewhere, which are about *branches*, not *repos*. For now, ignore the special usernames READERS and WRITERS, and just create a new repo, as user "u4" (a student): $ git clone git@server:assignments/u4/a12 Initialized empty Git repository in /home/sitaram/t/a12/.git/ Initialized empty Git repository in /home/gitolite/repositories/assignments/u4/a12.git/ warning: You appear to have cloned an empty repository. Notice the *two* empty repo inits, and the order in which they occur ;-) ### wildcard repos without creator name in them Here's how the same example would look if you did not want the CREATOR's name to be part of the actual repo name. repo assignments/a[0-9][0-9] C = @students RW+ = CREATOR RW = WRITERS @TAs R = READERS @prof We haven't changed anything except the repo name pattern. This means that the first student that creates, say, `assignments/a12` becomes the owner. Mistakes (such as claiming a12 instead of a13) need to be rectified by an admin logging on to the back end, though it's not too difficult. You could also repace the C line like this: C = @TAs and have a TA create the repos in advance. In either case, they could then use the `setperms` feature to specify which users are "READERS" and which are "WRITERS". See later for details. ## F=wildregex valid regexes and how they are used ### side-note: valid regexes Due to projects like `gtk+`, the `+` character is now considered a valid character for an *ordinary* repo. Therefore, a pattern like `foo/.+` does not look like a regex to gitolite. Use `foo/..*` if you want that. Also, `..*` by itself is not considered a valid repo pattern. Try `[a-zA-Z0-9].*`. ### side-note: line-anchored regexes A regex like repo assignments/S[0-9]+/A[0-9]+ would match `assignments/S02/A37`. It will not match `assignments/S02/ABC`, or `assignments/S02/a37`, obviously. But you may be surprised to find that it does not match even `assignments/S02/A37/B99`. This is because internally, gitolite *line-anchors* the given regex; so that regex actually becomes `^assignments/S[0-9]+/A[0-9]+$` -- notice the line beginning and ending metacharacters. #### contrast with refexes Just for interest, note that this is in contrast to the refexes for the normal "branch" permissions, as described in `doc/gitolite.conf.mkd` and elsewhere. These "refexes" are only anchored at the start; a pattern like `refs/heads/master` actually can match `refs/heads/master01/bar` as well, even if no one will actually push such a branch! You can anchor both sides if you really care, by using `master$` instead of `master`, but that is *not* the default for refexes. ## F=setperms handing out rights to wildcard-matched repos In the examples above, we saw two special "user" names: READERS and WRITERS. The permissions they have are controlled by the config file, but ***who is part of this list*** is controlled by the person who created the repository. The use case is that, although our toy example has only 3 students, in reality there will be a few dozen, but each assignment will be worked on only by a handful from among those. This allows the creator to take ad hoc sets of users from among the actual users in the system, and give them one of two roles (in this example, READERS and WRITERS respectively). In theory you could do the same thing by creating lots of little "assignment-NN" groups in the config file but that may be a little too cumbersome for non-secret environments. Create a small text file that contains the permissions you desire: $ cat > myperms READERS u5 WRITERS u6 (hit ctrl-d here) ...and use the new **setperms** command to set permissions for your repo: $ ssh git@server setperms assignments/u4/a12 < myperms New perms are: READERS u5 WRITERS u6 'setperms' will helpfully print what the new permissions are but you can also use **getperms** to check: $ ssh git@server getperms assignments/u4/a12 READERS u5 WRITERS u6 The following points are important: * note the syntax of the command; it's not a "git" command, and there's no `:` like in a repo URL. * for the actual text being sent in via STDIN, the first space-separated word is the role (in this example, READERS or WRITERS), and the rest are simple usernames. ### (admin) adding other roles than READERS and WRITERS Let's say your needs are more complex and you need more roles. For example, you might like to have a setup where only a tester can update tags, and only a manager can delete branches: repo foo/..* C = u1 RW refs/tags/ = TESTERS - refs/tags/ = @all RW+ = WRITERS RW = INTERNS R = READERS RW+D = MANAGERS As you can see, someone pre-creates the repo and assigns rights to various people, say by sending something like this to `setperms`: READERS wally WRITERS dilbert alice MANAGERS phb INTERNS ashok TESTERS ashok You can enable this by setting the `GL_WILDREPOS_PERM_CATS` variable in the rc file. The rc file documentation (`doc/gitolite.rc.mkd`) explains how. #### #rolenamewarn **IMPORTANT WARNING ABOUT THIS FEATURE** Please make sure that none of the role names conflict with any of the **usernames** in the system. For example, if you have a user called "foo", make sure you do not include "foo" as a valid role in `$GL_WILDREPOS_PERM_CATS`. You can keep things sane by using UPPERCASE names for roles, while keeping all your usernames lowercase; then you don't have to worry about this problem. ## setting a gitweb description for a wildcard-matched repo Similar to the getperms/setperms commands, there are the [getdesc/setdesc][setdesc] commands, thanks to Teemu. ## reporting In order to see what repositories were created from a wildcard, use the ["expand"][expand] command. ## deleting a wild repo See [repo deletion][wild_repodel] for more on this. Note that this requires you to install/setup "adc"s (admin defined commands). See [admin-defined-commands][ADCs] for how to do that. ---- Enjoy, and please use with care. This is pretty powerful stuff. As they say: if you break it, you get to keep both pieces :) ## F=wildhow_ how it actually works This section tells you what is happening inside gitolite so you can understand this feature better. Let's use the config example at the beginning of this document: repo assignments/CREATOR/a[0-9][0-9] C = @students RW+ = CREATOR RW = WRITERS @TAs R = READERS @prof First we find the set of rules to apply. This involves replacing the special words CREATOR, WRITERS, and READERS with appropriate usernames to derive an "effective" ruleset for the repo in question. For a **new** repo, replace the word CREATOR in all repo patterns and rules with the name of the invoking user. > (Note: this is why you should never use `C = CREATOR`; it becomes `C = > invoking_user`! Unless you really want to allow *all* users to create > repos, you should restrict "C" perms to an actual user or set of users, > like in the examples in this document). For an **existing** repo, do the same but replace with the name of the user who actually *created* the repo (this name is recorded in a special file in the repo directory when the repo is first created, so it is available). Now find a repo pattern that matches the actual reponame being pushed -- this tells you which set of rules to apply. There can be multiple matches; if so, they will all be applied in the sequence they appear in the config file. If the invoking user has been given the "WRITERS" role using `setperms`, all permissions for the the user WRITERS are given to the invoking username (and similarly for READERS). At this point we have an effective ruleset, and the normal access rules (R, RW, etc) apply, with the addition that the invoking user needs "C" access to be able to create a repo. > (Note: "C" rights do not automatically give the CREATOR any other rights; > they must be specifically given. `RW+ = CREATOR` is recommended in most > situations, as you can see in our example). Assuming user "u4" trying to push-create a new repo called `assignments/u4/a23`, this is what the effective ruleset looks like: repo assignments/u4/a23 C = @students RW+ = u4 RW = @TAs R = @prof If u4 gives u5 the "WRITERS" role using `setperms`, and u5 tries to access that repo, the ruleset looks like: repo assignments/u4/a23 C = @students RW+ = u4 RW = u5 @TAs R = @prof I hope that helps. sitaramc-gitolite-6857652/hooks/000077500000000000000000000000001171610012500164605ustar00rootroot00000000000000sitaramc-gitolite-6857652/hooks/common/000077500000000000000000000000001171610012500177505ustar00rootroot00000000000000sitaramc-gitolite-6857652/hooks/common/gitolite-hooked000066400000000000000000000000001171610012500227500ustar00rootroot00000000000000sitaramc-gitolite-6857652/hooks/common/gl-pre-git.hub-sample000066400000000000000000000004401171610012500236740ustar00rootroot00000000000000#!/bin/sh # sample pre-git hook to print pending hub requests [ "$1" = "R" ] && exit 0 # we only want to print them on pushes # print 'fetched' and 'pending' requests only SSH_ORIGINAL_COMMAND="hub list-requests $GL_REPO fetched pending" $GL_BINDIR/gl-auth-command $GL_USER exit 0 sitaramc-gitolite-6857652/hooks/common/post-receive.mirrorpush000077500000000000000000000014711171610012500245170ustar00rootroot00000000000000#!/bin/sh # gitolite mirroring # please see doc/mirroring.mkd for instructions on how to use this die() { echo $0 "(post-receive.mirrorpush):" "$@" >&2; exit 1; } # flush STDIN coming from git; we have no use for that info in this hook but # if you don't do this, git-shell sometimes dies of a signal 13 (SIGPIPE) [ -t 0 ] || cat >/dev/null # even slaves have post-receive hooks, so we bail when a mirrored push happens [ -n "$GL_BYPASS_UPDATE_HOOK" ] && exit 0 # CAUTION: a server-side push (bypassing gitolite) will not be mirrored # automatically because we don't know GL_REPO (we can deduce it but we won't!) [ -z "$GL_REPO" ] && die GL_REPO not set [ -z "$GL_BINDIR" ] && die GL_BINDIR not set slaves=`git config --get gitolite.mirror.slaves` [ -z "$slaves" ] && exit 0 $GL_BINDIR/gl-mirror-push $GL_REPO $slaves sitaramc-gitolite-6857652/hooks/common/update000077500000000000000000000117021171610012500211610ustar00rootroot00000000000000#!/usr/bin/perl use strict; use warnings; # === update === # this is gitolite's update hook # ---------------------------------------------------------------------------- # find the rc file, then pull the libraries # ---------------------------------------------------------------------------- BEGIN { # 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}; 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); # ---------------------------------------------------------------------------- # start... # ---------------------------------------------------------------------------- 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) chomp($merge_base = `git merge-base $oldsha $newsha`) unless $oldsha eq '0' x 40 or $newsha eq '0' x 40; # att_acc == attempted access -- what are you trying to do? (is it 'W' or '+'?) my $att_acc = 'W'; # rewriting a tag is considered a rewind, in terms of permissions $att_acc = '+' if $ref =~ m(refs/tags/) and $oldsha ne ('0' x 40); # non-ff push to ref # notice that ref delete looks like a rewind, as it should $att_acc = '+' if $oldsha ne $merge_base; # were any 'D' perms specified? If they were, it means we have to separate # deletes from rewinds, so if the new sha is all 0's, change the '+' to a 'D' $att_acc = 'D' if ( $repos{$ENV{GL_REPO}}{DELETE_IS_D} or $repos{'@all'}{DELETE_IS_D} ) and $newsha eq '0' x 40; # similarly C for create a branch $att_acc = 'C' if ( $repos{$ENV{GL_REPO}}{CREATE_IS_C} or $repos{'@all'}{CREATE_IS_C} ) and $oldsha eq '0' x 40; # and now "M" commits. This presents a bit of a problem. All the other # accesses (W, +, C, D) were mutually exclusive in some sense. Sure a W could # be a C or a + could be a D but that's by design. A merge commit, however, # could still be any of the others (except a "D"). # so we have to *append* 'M' to $att_acc (if the repo has MERGE_CHECK in # effect and this push contains a merge inside) if ( $repos{ $ENV{GL_REPO} }{MERGE_CHECK} or $repos{'@all'}{MERGE_CHECK} ) { if ( $oldsha eq '0' x 40 or $newsha eq '0' x 40 ) { warn "ref create/delete ignored for purposes of merge-check\n"; } else { $att_acc .= 'M' if `git rev-list -n 1 --merges $oldsha..$newsha` =~ /./; } } my @allowed_refs; # @all repos: see comments in similar code in check_access push @allowed_refs, @ { $repos{$ENV{GL_REPO}}{$ENV{GL_USER}} || [] }; push @allowed_refs, @ { $repos{'@all'} {$ENV{GL_USER}} || [] }; push @allowed_refs, @ { $repos{$ENV{GL_REPO}}{'@all'} || [] }; push @allowed_refs, @ { $repos{'@all'} {'@all'} || [] }; # prepare the list of refs to be checked # previously, we just checked $ref -- the ref being updated, which is passed # to us by git (see man githooks). Now we also have to treat each NAME being # updated as a potential "ref" and check that, if NAME-based restrictions have # been specified my @refs = ($ref); # the first ref to check is the real one # because making it work screws up efficiency like no tomorrow... if (exists $repos{$ENV{GL_REPO}}{NAME_LIMITS}) { # this is special to git -- the hash of an empty tree my $empty='4b825dc642cb6eb9a060e54bf8d69288fbee4904'; # well they're not really "trees" but $empty is indeed the empty tree so # we can just pretend $oldsha/$newsha are also trees, and anyway 'git # diff' only wants trees my $oldtree = $oldsha eq '0' x 40 ? $empty : $oldsha; my $newtree = $newsha eq '0' x 40 ? $empty : $newsha; push @refs, map { chomp; s/^/NAME\//; $_; } `git diff --name-only $oldtree $newtree`; } # 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; # if we returned at all, all the checks succeeded. Check secondary hooks now $UPDATE_CHAINS_TO ||= 'hooks/update.secondary'; -x $UPDATE_CHAINS_TO and system ( $UPDATE_CHAINS_TO, @ARGV ) and die "$UPDATE_CHAINS_TO died\n"; # now log it and exit 0 so git can get on with it log_it("", "$att_acc\t" . substr($oldsha, 0, 14) . "\t" . substr($newsha, 0, 14) . "\t$reported_repo\t$ref\t$log_refex"); exit 0; sitaramc-gitolite-6857652/hooks/common/update.secondary.sample000066400000000000000000000025751171610012500244340ustar00rootroot00000000000000#!/bin/sh # driver script to run multiple update "hooklets". Each "hooklet" performs a # specific (possibly site-local) check, and they *all* have to succeed for the # push to succeed. # HOW TO USE: # (1) rename this file to remove the .sample extension # (2) make the renamed file executable (chmod +x) # (3) put it in ~/.gitolite/hooks/common on the server # (4) create a directory called update.secondary.d in the same place # (5) copy all the update "hooklets" you want (like update.detect-dup-pubkeys) # from contrib or wherever, to that directory # (6) make them also executable (chmod +x) # (7) run gl-setup # rules for writing a hooklet are in the sample hooklet called # "update.detect-dup-pubkeys" in contrib # ---- # NOTE: a hooklet runs under the same assumptions as the 'update' hook, so the # starting directory must be maintained and arguments must be passed on. [ -d hooks/update.secondary.d ] || exit 0 # all output from these "hooklets" must go to STDERR to avoid confusing the client exec >&2 for i in hooks/update.secondary.d/* do [ -x "$i" ] || continue # call the hooklet with the same arguments we got "$i" "$@" || { # hooklet failed; we need to log it... echo hooklet $i failed perl -I$GL_BINDIR -Mgitolite -e "log_it('hooklet $i failed')" # ...and send back some non-zero exit code ;-) exit 1 } done exit 0 sitaramc-gitolite-6857652/hooks/gitolite-admin/000077500000000000000000000000001171610012500213665ustar00rootroot00000000000000sitaramc-gitolite-6857652/hooks/gitolite-admin/post-update000077500000000000000000000020671171610012500235660ustar00rootroot00000000000000#!/bin/sh die() { echo "$@" >&2; exit 1; } [ -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)$/' || { echo "*** ERROR ***" >&2 echo "no files/dirs called 'src' or 'hooks' are allowed, sorry" >&2 echo "until those files are deleted, the post-update hook will not run" >&2 exit 1 } # checkout the master branch to $GL_ADMINDIR # (the GL_ADMINDIR env var would have been set by gl-auth-command) GIT_WORK_TREE=$GL_ADMINDIR git checkout -f --quiet master od=$PWD cd $GL_ADMINDIR $GL_BINDIR/gl-compile-conf cd $od 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 [ -x $ADMIN_POST_UPDATE_CHAINS_TO ] then exec $ADMIN_POST_UPDATE_CHAINS_TO "$@" fi sitaramc-gitolite-6857652/src/000077500000000000000000000000001171610012500161245ustar00rootroot00000000000000sitaramc-gitolite-6857652/src/gitolite.pm000066400000000000000000001402201171610012500203010ustar00rootroot00000000000000# lots of common routines package gitolite; use Exporter 'import'; @EXPORT = qw( can_read check_access check_ref check_repo_write_enabled cli_repo_rights cli_grouplist dbg dos2unix 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 setup_web_access shell_out slurp special_cmd try_adc wrap_chdir wrap_open wrap_print mirror_mode mirror_listslaves mirror_redirectOK ); @EXPORT_OK = qw( %repos %groups %git_configs %split_conf ); use strict; use warnings; use Data::Dumper; $Data::Dumper::Deepcopy = 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}; } # ---------------------------------------------------------------------------- # register signal handlers to log any problems # ---------------------------------------------------------------------------- BEGIN { $SIG{__DIE__} = sub { my $msg = join(' ', "Die generated at line", (caller)[2], "in", (caller)[1], ":", @_, "\n"); $msg =~ s/[\n\r]+/<>/g; log_it($msg) if $ENV{GL_LOG}; }; $SIG{__WARN__} = sub { my $msg = join(' ', "Warn generated at line", (caller)[2], "in", (caller)[1], ":", @_, "\n"); $msg =~ s/[\n\r]+/<>/g; log_it($msg) if $ENV{GL_LOG}; warn @_; }; } use lib $ENV{GL_BINDIR}; use gitolite_rc; # silently disable URI escaping if the module is not found $GITWEB_URI_ESCAPE &&= eval "use CGI::Util qw(escape); 1"; # ---------------------------------------------------------------------------- # the big data structures we care about # ---------------------------------------------------------------------------- our %repos; our %groups; our %git_configs; our %split_conf; our $data_version; # the following are read in from individual repo's gl-conf files, if present our %one_repo; # corresponds to what goes into %repos our %one_git_config; # ditto for %git_configs # ---------------------------------------------------------------------------- # convenience subs # ---------------------------------------------------------------------------- sub wrap_chdir { chdir($_[0]) or die "$ABRT chdir $_[0] failed: $! at ", (caller)[1], " line ", (caller)[2], "\n"; } sub wrap_open { open (my $fh, $_[0], $_[1]) or die "$ABRT open $_[1] failed: $! at ", (caller)[1], " line ", (caller)[2], "\n" . ( $_[2] || '' ); # suffix custom error message if given return $fh; } sub wrap_print { my ($file, @text) = @_; my $fh = wrap_open(">", "$file.$$"); print $fh @text; close($fh) or die "$ABRT close $file failed: $! at ", (caller)[1], " line ", (caller)[2], "\n"; my $oldmode = ( (stat $file)[2] ); rename "$file.$$", $file; chmod $oldmode, $file if $oldmode; } sub slurp { local $/ = undef; my $fh = wrap_open("<", $_[0]); return <$fh>; } sub add_del_line { my ($line, $file, $op, $escape) = @_; # $op is true for add operation, false for delete # $escape is true if the lines needs to be URI escaped my $contents; $line = escape($line) if $escape; local $/ = undef; my $fh = wrap_open("<", $file); $contents = <$fh>; $contents =~ s/\s+$/\n/; if ($op and $contents !~ /^\Q$line\E$/m) { # add line if it doesn't exist $contents .= "$line\n"; wrap_print($file, $contents); } if (not $op and $contents =~ /^\Q$line\E$/m) { $contents =~ s/^\Q$line\E(\n|$)//m; wrap_print($file, $contents); } } sub dbg { use Data::Dumper; for my $i (@_) { print STDERR "DBG: " . Dumper($i); } } sub dos2unix { # WARNING: when calling this, make sure you supply a list context s/\r\n/\n/g for @_; return @_; } sub log_it { my ($ip, $logmsg); open my $log_fh, ">>", $ENV{GL_LOG} or die "open log failed: $!\n"; # first space sep field is client ip, per "man ssh" ($ip = $ENV{SSH_CONNECTION} || '(no-IP)') =~ s/ .*//; # the first part of logmsg is the actual command used; it's either passed # in via arg1, or picked up from SSH_ORIGINAL_COMMAND $logmsg = $_[0] || $ENV{SSH_ORIGINAL_COMMAND}; shift; # the rest of it upto the caller; we just dump it into the logfile $logmsg .= "\t@_" if @_; # erm... this is hard to explain so just see the commit message ok? $logmsg =~ s/([\x00-\x08\x0B\x0C\x0E-\x1F\x7F-\xFF]+)/sprintf "<>","",$1/ge; my $user = $ENV{GL_USER} || "(no user)"; print $log_fh "$ENV{GL_TS}\t$user\t$ip\t$logmsg\n"; close $log_fh or die "close log failed: $!\n"; } # ln -sf :-) sub ln_sf { my($srcdir, $glob, $dstdir) = @_; for my $hook ( glob("$srcdir/$glob") ) { $hook =~ s/$srcdir\///; unlink "$dstdir/$hook"; symlink "$srcdir/$hook", "$dstdir/$hook" or die "could not symlink $srcdir/$hook to $dstdir\n"; } } # list physical repos sub list_phy_repos { my @phy_repos; wrap_chdir($REPO_BASE); for my $repo (`find . -type d -name "*.git" -prune`) { chomp ($repo); $repo =~ s(\./(.*)\.git$)($1); push @phy_repos, $repo; } return @phy_repos; } # ---------------------------------------------------------------------------- # serious logic subs (as opposed to just "convenience" subs) # ---------------------------------------------------------------------------- # 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 C/D if used) being # attempted. If none of them match, the access is denied. # NOTE: the function DIES when access is denied, unless arg 5 is true my ($allowed_refs, $repo, $ref, $perm, $dry_run) = @_; # sanity check the ref die "invalid characters in ref or filename: $ref\n" unless $ref =~ $GL_REF_OR_FILENAME_PATT; 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/ or $ref eq 'joker'; # joker matches any refex; it will only ever be sent internally return "$perm $ref $repo $ENV{GL_USER} DENIED by $refex" if $ar->[2] eq '-' and $dry_run; die "$perm $ref $repo $ENV{GL_USER} DENIED by $refex\n" if $ar->[2] eq '-'; # $ar->[2] can be RW\+?(C|D|CD|DC)?M?. $perm can be W, +, C or # D, or any of these followed by "M". ( my $permq = $perm ) =~ s/\+/\\+/; $permq =~ s/M/.*M/; # as far as *this* ref is concerned we're ok return $refex if ($ar->[2] =~ /$permq/); } return "$perm $ref $repo $ENV{GL_USER} DENIED by fallthru" if $dry_run; die "$perm $ref $repo $ENV{GL_USER} DENIED by fallthru\n"; } # ---------------------------------------------------------------------------- # create a new repository # ---------------------------------------------------------------------------- # NOTE: this sub will change your cwd; caller beware! sub new_repo { my ($repo, $hooks_dir, $creator) = @_; umask($REPO_UMASK); die "wildrepos disabled, can't set creator $creator on new repo $repo\n" if $creator and not $GL_WILDREPOS; system("mkdir", "-p", "$repo.git") and die "$ABRT mkdir $repo.git failed: $!\n"; # erm, note that's "and die" not "or die" as is normal in perl wrap_chdir("$repo.git"); system("git --bare init >&2"); if ($creator) { wrap_print("gl-creater", $creator); system("git", "config", "gitweb.owner", $creator); } # propagate our own, plus any local admin-defined, hooks ln_sf($hooks_dir, "*", "hooks"); # in case of package install, GL_ADMINDIR is no longer the top cop; # override with the package hooks ln_sf("$GL_PACKAGE_HOOKS/common", "*", "hooks") if $GL_PACKAGE_HOOKS; chmod 0755, "hooks/update"; # run gitolite's post-init hook if you can. GL_REPO will be correct on a # wildcard create but on a normal (config file) create it will actually be # set to "gitolite-admin", so we need to make sure that for the duration # of the hook it is set correctly. system("env", "GL_REPO=$repo", "hooks/gl-post-init") if -x "hooks/gl-post-init"; } sub new_wild_repo { my ($repo, $user) = @_; wrap_chdir($REPO_BASE); 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_web_access($repo); wrap_chdir($ENV{HOME}); } # ---------------------------------------------------------------------------- # wild_repo_rights # ---------------------------------------------------------------------------- { # the following subs need some persistent data, so we make a closure my $cache_filled = 0; my %cached_groups; sub fill_cache { # pull in basic group info unless ($cache_filled) { local(%repos, %groups); local $^W = 0; # read group info from compiled config. At the time we're called # this info has not yet been pulled in by the rest of the code, so # we need to do this specially here. However, the info we're # looking for is not subject to variable substitutions so we don't # really care; we just pull it in once and save it for the rest of # the run do $GL_CONF_COMPILED; %cached_groups = %groups; $cache_filled++; } } # "who created this repo", "am I on the R list", and "am I on the RW list"? sub wild_repo_rights { # set default categories $GL_WILDREPOS_PERM_CATS ||= "READERS WRITERS"; my ($repo, $user) = @_; # creator my $c = ''; if ( -f "$REPO_BASE/$repo.git/gl-creater") { my $fh = wrap_open("<", "$REPO_BASE/$repo.git/gl-creater"); chomp($c = <$fh>); } # now get the permission categories (used to be just R and RW. Now # there can be any others that the admin defines in the RC file via # $GL_WILDREPOS_PERM_CATS variable (space separated list) # For instance, if the user is "foo", and gl-perms has "R bar", "RW # foo baz", and "TESTERS frob @all", this hash will then contain # "WRITERS=>foo" and "TESTERS=>@all" my %perm_cats; if ($user and -f "$REPO_BASE/$repo.git/gl-perms") { my ($perms) = dos2unix(slurp("$REPO_BASE/$repo.git/gl-perms")); # discard comments $perms =~ s/#.*//g; # convert R and RW to the actual category names in the config file $perms =~ s/^\s*R /READERS /mg; $perms =~ s/^\s*RW /WRITERS /mg; # $perms is say "READERS alice @foo @bar\nRW bob @baz" (the entire gl-perms # file). We replace each @foo with $user if $cached_groups{'@foo'}{$user} # exists (i.e., $user is a member of @foo) for my $g ($perms =~ /\s(\@\S+)/g) { fill_cache(); # get %cached_groups $perms =~ s/ $g(?!\S)/ $user/ if $cached_groups{$g}{$user}; } # now setup the perm_cats hash to be returned if ($perms) { # let's say our user is "foo". gl-perms has "CAT bar @all", # you add CAT => @all to the hash. similarly, if gl-perms has # "DOG bar foo baz", you add DOG => foo to the hash. And # since specific perms must override @all, we do @all first. $perm_cats{$1} = '@all' while ($perms =~ /^[ \t]*(\S+)(?=[ \t]).*[ \t]\@all([ \t]|$)/mg); $perm_cats{$1} = $user while ($perms =~ /^[ \t]*(\S+)(?=[ \t]).*[ \t]$user([ \t]|$)/mg); # validate the categories being sent back for (sort keys %perm_cats) { die "invalid permission category $_\n" unless $GL_WILDREPOS_PERM_CATS =~ /(^|\s)$_(\s|$)/; } } } return ($c, %perm_cats); } } # ---------------------------------------------------------------------------- # getperms and setperms # ---------------------------------------------------------------------------- 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, ""); die "$repo doesnt exist or is not yours\n" unless $user eq $creator; wrap_chdir($REPO_BASE); wrap_chdir("$repo.git"); if ($verb eq 'getperms') { return unless -f "gl-perms"; my $perms = slurp("gl-perms"); # convert R and RW to the actual category names in the config file $perms =~ s/^\s*R /READERS /mg; $perms =~ s/^\s*RW /WRITERS /mg; print $perms; } else { wrap_print("gl-perms", <>); # eqvt to: system("cat > gl-perms"); my $perms = slurp("gl-perms"); # convert R and RW to the actual category names in the config file $perms =~ s/^\s*R /READERS /mg; $perms =~ s/^\s*RW /WRITERS /mg; for my $g ($perms =~ /^\s*(\S+)/gm) { die "invalid permission category $g\n" unless $g =~ /^#/ or $GL_WILDREPOS_PERM_CATS =~ /(^|\s)$g(\s|$)/; } print "New perms are:\n"; print $perms; # gitweb and daemon setup_daemon_access($repo); # add or delete line (arg1) from file (arg2) depending on arg3 add_del_web_access($repo); } } # ---------------------------------------------------------------------------- # getdesc and setdesc # ---------------------------------------------------------------------------- sub get_set_desc { my($repo, $verb, $user) = @_; my ($creator, $dummy, $dummy2) = wild_repo_rights($repo, ""); die "$repo doesnt exist or is not yours\n" unless $user eq $creator; wrap_chdir($REPO_BASE); wrap_chdir("$repo.git"); if ($verb eq 'getdesc') { print slurp("description") if -f "description"; } else { wrap_print("description", <>); print "New description is:\n"; print slurp("description"); } } # ---------------------------------------------------------------------------- # IMPORTANT NOTE: next 3 subs (setup_*) assume $PWD is the bare repo itself # ---------------------------------------------------------------------------- # ---------------------------------------------------------------------------- # set/unset git configs # ---------------------------------------------------------------------------- sub setup_git_configs { return if $GL_NO_DAEMON_NO_GITWEB; my ($repo, $git_configs_p) = @_; # new_wild calls us without checking! return unless $git_configs_p->{$repo}; # git_configs_p is a ref to a hash whose elements look like # {"reponame"}{sequence_number}{"key"} = "value"; my %rch = %{ $git_configs_p->{$repo} }; # %rch has elements that look like {sequence_number}{"key"} = "value" for my $seq (sort { $a <=> $b } keys %rch) { # and the final step is the repo config: {"key"} = "value" my $rc = $rch{$seq}; while ( my ($key, $value) = each(%{ $rc }) ) { next if $key =~ /^gitolite-options\./; if ($value ne "") { $value =~ s/^['"](.*)["']$/$1/; system("git", "config", $key, $value); } else { system("git", "config", "--unset-all", $key); } } } } # ---------------------------------------------------------------------------- # set/unset daemon access # ---------------------------------------------------------------------------- # does not return anything; just touch/unlink the appropriate file my $export_ok = "git-daemon-export-ok"; sub setup_daemon_access { return if $GL_NO_DAEMON_NO_GITWEB; my $repo = shift; if (can_read($repo, 'daemon')) { wrap_print($export_ok, ""); } else { unlink($export_ok); } } # ---------------------------------------------------------------------------- # set/unset gitweb access # ---------------------------------------------------------------------------- sub setup_web_access { # input is a hashref; keys are project names if ($WEB_INTERFACE eq 'gitweb') { my $projlist = shift; my $projlist_fh = wrap_open( ">", "$PROJECTS_LIST.$$"); for my $proj (sort keys %{ $projlist }) { print $projlist_fh "" . ( $GITWEB_URI_ESCAPE ? escape($proj) : $proj ) . "\n"; } close $projlist_fh; rename "$PROJECTS_LIST.$$", $PROJECTS_LIST; } else { warn "sorry, unknown web interface $WEB_INTERFACE\n"; } } sub add_del_web_access { return if $GL_NO_DAEMON_NO_GITWEB; # input is a repo name. Code could simply use `can_read($repo, 'gitweb')` # to determine whether to add or delete the repo from web access. # However, "desc" also factors into this so we have think about this. if ($WEB_INTERFACE eq 'gitweb') { my $repo = shift; add_del_line ("$repo.git", $PROJECTS_LIST, setup_gitweb_access($repo, '', '') || 0, $GITWEB_URI_ESCAPE || 0); } else { warn "sorry, unknown web interface $WEB_INTERFACE\n"; } } # returns 1 if gitweb access has happened; this is to allow the caller to add # an entry to the projects.list file my $desc_file = "description"; sub setup_gitweb_access # this also sets "owner" for gitweb, by the way { my ($repo, $desc, $owner) = @_; my $is_wild = -f "gl-creater"; # we may override but we do not remove gitweb.owner and description # for wild repos if ($desc) { open(DESC, ">", $desc_file); print DESC $desc . "\n"; close DESC; } else { unlink $desc_file unless $is_wild; } if ($owner) { system("git", "config", "gitweb.owner", $owner); } else { system("git config --unset-all gitweb.owner 2>/dev/null") unless $is_wild; } # if there are no gitweb.* keys set, remove the section to keep the config file clean my $keys = `git config --get-regexp '^gitweb\\.' 2>/dev/null`; if (length($keys) == 0) { system("git config --remove-section gitweb 2>/dev/null"); } 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) = @_; my $gl_version = slurp( ($GL_PACKAGE_CONF || "$GL_ADMINDIR/conf") . "/VERSION" ); chomp($gl_version); my $git_version = `git --version`; $git_version =~ s/^git version //; print "hello $user, this is gitolite $gl_version running on git $git_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 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 next if $GL_BIG_CONFIG and $count++ >= $BIG_INFO_CAP; 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 # (Note: this now includes the rarely useful "@all users allowed # access to @all repos" case) # #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}{'@all'}, $repos{'@all'}{R}{$user}, $repos{$r}{R}{$user}, 'R'); $perm .= perm_code( $repos{$r}{W}{'@all'} || $repos{'@all'}{W}{'@all'}, $repos{'@all'}{W}{$user}, $repos{$r}{W}{$user}, 'W'); print "$perm\t$r\r\n" if $perm =~ /\S/ and not check_deny_repo($r); } print "only $BIG_INFO_CAP 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 > $BIG_INFO_CAP; 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($REPO_BASE) or die "chdir $REPO_BASE failed: $!\n"; my $count = 0; for my $actual_repo (`find . -type d -name "*.git" -prune|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++ >= $BIG_INFO_CAP; my($perm, $creator, $wild) = repo_rights($actual_repo); next unless $perm =~ /\S/; print "$perm\t$creator\t$actual_repo\n"; } print "only $BIG_INFO_CAP out of $count candidate repos examined\nplease use a partial reponame or regex pattern to limit output\n" if $GL_BIG_CONFIG and $count > $BIG_INFO_CAP; print "$GL_SITE_INFO\n" if $GL_SITE_INFO; } # ---------------------------------------------------------------------------- # parse the compiled acl # ---------------------------------------------------------------------------- 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 ($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; # set up the variables for a parse to interpolate stuff from the dumped # hash (remember the selective conversion of single to double quotes?). # if they're not passed in, then we look for an env var of that name, else # we default to "NOBODY" (we hope there isn't a real user called NOBODY!) # And in any case, we set those env vars so level 2 can redo the last # parse without any special code our $creator = $ENV{GL_CREATOR} = $c || $ENV{GL_CREATOR} || "NOBODY"; our $gl_user = $ENV{GL_USER}; # these need to persist across calls to this function, so "our" our $saved_crwu; our (%saved_repos, %saved_groups); if ($saved_crwu and $saved_crwu eq "$creator,$perm_cats_sig,$gl_user") { %repos = %saved_repos; %groups = %saved_groups; } else { die "parse $GL_CONF_COMPILED failed: " . ($! or $@) unless do $GL_CONF_COMPILED; } unless (defined($data_version) and $data_version eq $current_data_version) { warn "(INTERNAL: $data_version -> $current_data_version; running gl-setup)\n"; system("$ENV{SHELL} -l -c gl-setup >&2"); die "parse $GL_CONF_COMPILED failed: " . ($! or $@) unless do $GL_CONF_COMPILED; } $saved_crwu = "$creator,$perm_cats_sig,$gl_user"; %saved_repos = %repos; %saved_groups = %groups; add_repo_conf($repo) if $repo; # basic access reporting doesn't send $repo, and doesn't need to; you just # want the config dumped as is, really return unless $repo; 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); # the old "convenience copy" thing. Now on steroids :) # note that when copying the @all entry, we retain the destination name as # @all; we dont change it to $repo or $gl_user. We need to maintain this # distinction to be able to print the @/#/& prefixes in the report output # (see doc/report-output.mkd) for my $r ('@all', @repo_plus) { my $dr = $repo; $dr = '@all' if $r eq '@all'; $repos{$dr}{DELETE_IS_D} = 1 if $repos{$r}{DELETE_IS_D}; $repos{$dr}{CREATE_IS_C} = 1 if $repos{$r}{CREATE_IS_C}; $repos{$dr}{NAME_LIMITS} = 1 if $repos{$r}{NAME_LIMITS}; $repos{$dr}{MERGE_CHECK} = 1 if $repos{$r}{MERGE_CHECK}; # this needs to copy the key-value pairs from RHS to LHS, not just # assign RHS to LHS! However, we want to roll in '@all' configs also # into the actual $repo; there's no need to preserve the distinction map { $git_configs{$repo}{$_} = $git_configs{$r}{$_} } keys %{$git_configs{$r}} if $git_configs{$r}; for my $u ('@all', "$gl_user - wild", @user_plus, keys %perm_cats) { my $du = $gl_user; $du = '@all' if $u eq '@all' or ($perm_cats{$u} || '') eq '@all'; $repos{$dr}{C}{$du} = 1 if $repos{$r}{C}{$u}; $repos{$dr}{R}{$du} = 1 if $repos{$r}{R}{$u}; $repos{$dr}{W}{$du} = 1 if $repos{$r}{W}{$u}; next if $r eq $dr and $u eq $du; # no point duplicating those refexes push @{ $repos{$dr}{$du} }, @{ $repos{$r}{$u} } if exists $repos{$r}{$u} and ref($repos{$r}{$u}) eq 'ARRAY'; } } return ($wild); } # add repo conf from repo.git/gl-conf sub add_repo_conf { my ($repo) = shift; return unless $split_conf{$repo}; do "$REPO_BASE/$repo.git/gl-conf" or return; $repos{$repo} = $one_repo{$repo}; $git_configs{$repo} = $one_git_config{$repo}; } # ---------------------------------------------------------------------------- # repo_rights # ---------------------------------------------------------------------------- # 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 # already defined { my $last_repo = ''; sub repo_rights { my $repo = shift; $repo =~ s/^\.\///; $repo =~ s/\.git$//; # we get passed an actual repo name. It may be a normal # (non-wildcard) repo, in which case it is assumed to exist. If it's # a wildrepo, it may or may not exist. If it doesn't exist, the "C" # perms are also filled in, else that column is left blank unless ($REPO_BASE) { # means we've been called from outside; see doc/admin-defined-commands.mkd where_is_rc(); die "parse $ENV{GL_RC} failed: " . ($! or $@) unless do $ENV{GL_RC}; # fix up REPO_BASE $REPO_BASE = "$ENV{HOME}/$REPO_BASE" unless $REPO_BASE =~ m(^/); } my $perm = ' '; my $creator; # get basic info about the repo and fill %repos my $wild = ''; my $exists = -d "$REPO_BASE/$repo.git"; if ($exists) { # the list of permission categories within gl-perms that this user is a member # of, or that specify @all as a member. See comments in # "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}); # get access list with these substitutions $wild = parse_acl($repo, $creator || "NOBODY", %perm_cats); } else { $wild = parse_acl($repo, $ENV{GL_USER}); } if ($exists) { if ($creator and $wild) { $creator = "($creator)"; } elsif ($creator and not $wild) { # was created wild but then someone (a) removed the pattern # from, and (b) added the actual reponame to, the config $creator = "" } else { $creator = ""; } } else { # repo didn't exist; C perms need to be filled in $perm = ( $repos{$repo}{C}{'@all'} ? ' @C' : ( $repos{$repo}{C}{$ENV{GL_USER}} ? ' =C' : ' ' )) if $GL_WILDREPOS; # if you didn't have perms to create it, delete the "convenience" # copy of the ACL that parse_acl makes delete $repos{$repo} if $perm !~ /C/ and $wild; $creator = ""; } $perm .= perm_code( $repos{$repo}{R}{'@all'} || $repos{'@all'}{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}{'@all'}, $repos{'@all'}{W}{$ENV{GL_USER}}, $repos{$repo}{W}{$ENV{GL_USER}}, 'W' ); $perm =~ s/./ /g if check_deny_repo($repo); # set up for caching %repos $last_repo = $repo; return($perm, $creator, $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"; } # helper/convenience routine to get group membership info sub cli_grouplist { die "GL_BIG_CONFIG needs to be set\n" unless $GL_BIG_CONFIG; # we may not have any data yet... parse_acl() unless (%repos); my @groups = grep { s/^@//; } get_memberships($ENV{GL_USER}, 0); print join(" ", @groups), "\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); return ( ($GL_ALL_INCLUDES_SPECIAL || $user !~ /^(gitweb|daemon)$/) ? $perm =~ /R/ : $perm =~ /R / ); } # helper to manage "disabling" a repo or the whole site for "W" access sub check_repo_write_enabled { my ($repo) = shift; for my $d ("$ENV{HOME}/.gitolite.down", "$REPO_BASE/$repo.git/.gitolite.down") { next unless -f $d; die $ABRT . slurp($d) if -s $d; die $ABRT . "writes are currently disabled\n"; } } sub check_deny_repo { my $repo = shift; return 0 unless check_config_key($repo, "gitolite-options.deny-repo"); # there are no 'gitolite-options.deny-repo' keys # the 'joker' ref matches any refex. Think of it like a ".*" in reverse. # A pattern of ".*" matches any string. Similarly a string called 'joker' # matches any pattern :-) See check_ref() for implementation. return 1 if ( check_access($repo, 'joker', 'R', 1) ) =~ /DENIED by/; return 0; } sub check_config_key { my($repo, $key) = @_; my @ret = (); return () unless exists $git_configs{$repo}; # otherwise it auto-vivifies if you call it from new_repo() and causes # harmless but annoying entries in the compiled config file. They # disappear on the next compile of course, but still... # look through $git_configs{$repo} and return an array of the values of # all second level keys that match $key. To understand "second level", # you need to remember that %git_configs has elements like this: # $git_config{'reponame'}{sequence_number}{key} = value for my $s (sort { $a <=> $b } keys %{ $git_configs{$repo} }) { for my $k (keys %{ $git_configs{$repo}{$s} }) { push @ret, $git_configs{$repo}{$s}{$k} if $k =~ /^$key$/; } } return @ret; } # ---------------------------------------------------------------------------- # 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); unless ($ref) { ($perm, $creator, $wild) = repo_rights($repo); $perm =~ s/ /_/g; $creator =~ s/^\(|\)$//g; return ($perm, $creator); } ($perm, $creator, $wild) = repo_rights($repo) unless $ref eq 'joker'; # calling it when ref eq joker is infinitely recursive! check_access # will only be called with ref eq joker only when repo_rights has # already been called and %repos populated already. (See comments # elsewhere for what 'joker' is and why it is called that). # 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'} || [] }; push @allowed_refs, @ { $repos{'@all'}{'@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 # ---------------------------------------------------------------------------- sub setup_authkeys { # ARGUMENTS 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; # LOCAL CONSTANTS # command and options for authorized_keys 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; # set default authentication options $AUTH_OPTIONS ||= "no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty"; # START my $authkeys_fh = wrap_open( "<", $ENV{HOME} . "/.ssh/authorized_keys", "\tFor security reasons, gitolite will not *create* this file if it does\n" . "\tnot already exist. Please see the \"admin\" document for details\n"); my $newkeys_fh = wrap_open( ">", $ENV{HOME} . "/.ssh/new_authkeys" ); # save existing authkeys minus the GL-added stuff while (<$authkeys_fh>) { print $newkeys_fh $_ unless (/^# gito(sis-)?lite start/../^# gito(sis-)?lite end/); } # add our "start" line, each key on its own line (prefixed by command and # options, in the standard ssh authorized_keys format), then the "end" line. print $newkeys_fh "# gitolite start\n"; wrap_chdir($GL_KEYDIR); my @not_in_config; # pubkeys exist but users don't appear in the config file for my $pubkey (`find . -type f | sort`) { chomp($pubkey); $pubkey =~ s(^\./)(); # security check (thanks to divVerent for catching this) unless ($pubkey =~ $REPONAME_PATT) { warn "$pubkey contains some unsavoury characters; ignored...\n"; next; } # lint check 1 unless ($pubkey =~ /\.pub$/) { print STDERR "WARNING: pubkey files should end with \".pub\", ignoring $pubkey\n"; next; } my $user = $pubkey; $user =~ s(.*/)(); # foo/bar/baz.pub -> baz.pub $user =~ s/(\@[^.]+)?\.pub$//; # baz.pub, baz@home.pub -> baz # lint check 2 -- don't print right now; just collect the messages push @not_in_config, "$user($pubkey)" if %$user_list_p and not $user_list_p->{$user}; $user_list_p->{$user} = 'has pubkey' if %$user_list_p; # apparently some pubkeys don't end in a newline... my $pubkey_content; { local $/ = undef; local @ARGV = ($pubkey); $pubkey_content = <>; $pubkey_content =~ s/^\s*#.*\n//gm; } $pubkey_content =~ s/\s*$/\n/; # don't trust files with multiple lines (i.e., something after a newline) if ($pubkey_content =~ /\n./) { warn "WARNING: a pubkey file can only have one line (key); ignoring $pubkey\n" . " Perhaps you're using a key in a different format (like putty/plink)?\n" . " If so, please convert it to openssh format using 'ssh-keygen -i'.\n" . " If you want to add multiple public keys for a single user, use\n" . " \"user\@host.pub\" file names. See the \"one user, many keys\"\n" . " section in doc/3-faq-tips-etc.mkd for details.\n"; next; } print $newkeys_fh "command=\"$AUTH_COMMAND $user\",$AUTH_OPTIONS "; print $newkeys_fh $pubkey_content; } # lint check 2 -- print less noisily if (@not_in_config > 10) { print STDERR "$WARN You have " . scalar(@not_in_config) . " pubkeys that do not appear to be used in any access rules\n"; } elsif (@not_in_config) { print STDERR "$WARN the following users (pubkey files in parens) do not appear in any access rules:\n", join(",", sort @not_in_config), "\n"; } # lint check 3; a little more severe than the first two I guess... { my @no_pubkey = grep { $_ !~ /^(gitweb|daemon|\@.*|~\$creator)$/ } grep { $user_list_p->{$_} ne 'has pubkey' } grep { $GL_WILDREPOS_PERM_CATS !~ /(^|\s)$_(\s|$)/ } keys %{$user_list_p}; if (@no_pubkey > 10) { print STDERR "$WARN You have " . scalar(@no_pubkey) . " users WITHOUT pubkeys...!\n"; } elsif (@no_pubkey) { print STDERR "$WARN the following users have no pubkeys:\n", join(",", sort @no_pubkey), "\n"; } } print $newkeys_fh "# gitolite end\n"; close $newkeys_fh or die "$ABRT close newkeys failed: $!\n"; # all done; overwrite the file (use cat to avoid perm changes) system("cat $ENV{HOME}/.ssh/authorized_keys > $ENV{HOME}/.ssh/old_authkeys"); system("cat $ENV{HOME}/.ssh/new_authkeys > $ENV{HOME}/.ssh/authorized_keys") and die "couldn't write authkeys file\n"; system("rm $ENV{HOME}/.ssh/new_authkeys"); } # ---------------------------------------------------------------------------- # S P E C I A L C O M M A N D S # ---------------------------------------------------------------------------- sub special_cmd { 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('^', $user); print "you also have shell access\r\n" if $shell_allowed; } elsif ($cmd =~ /^info\s+(.+)$/) { my @otherusers = split ' ', $1; # the first argument is assumed to be a repo pattern, like in the # expand command my $repo = shift(@otherusers); die "$repo has invalid characters" unless "x$repo" =~ $REPOPATT_PATT; print STDERR "(treating $repo as pattern to limit output)\n"; # 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'); die "you can't ask for others' permissions\n" unless $perm =~ /W/; } push @otherusers, $user unless @otherusers; parse_acl(); for my $otheruser (@otherusers) { warn("ignoring illegal username $otheruser\n"), next unless $otheruser =~ $USERNAME_PATT; report_basic($repo, $otheruser); } } else { # if the user is allowed a shell, just run the command log_it(); exec $ENV{SHELL}, "-c", $cmd if $shell_allowed; die "bad command: $cmd\n"; } } sub run_custom_command { my $user = shift; 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($repo, $user); } else { 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}; die "I don't like $cmd\n" if $cmd =~ /\.\./; # try the default (strict arguments) version first if (-x "$GL_ADC_PATH/$cmd") { # yes this is rather strict, sorry. do { die "I don't like $_\n" unless $_ =~ $ADC_CMD_ARGS_PATT and $_ !~ m(\.\./) } for ($cmd, @args); log_it("$GL_ADC_PATH/$ENV{SSH_ORIGINAL_COMMAND}"); exec("$GL_ADC_PATH/$cmd", @args); } # now the "ua" (unrestricted/unchecked arguments) version if (-x "$GL_ADC_PATH/ua/$cmd") { log_it("$GL_ADC_PATH/ua/$ENV{SSH_ORIGINAL_COMMAND}"); exec("$GL_ADC_PATH/ua/$cmd", @args); } } # ---------------------------------------------------------------------------- # MIRRORING HELPERS # ---------------------------------------------------------------------------- sub mirror_mode { my $repo = shift; # 'local' is the default if the config is empty or not set my $gmm = `git config --file $REPO_BASE/$repo.git/config --get gitolite.mirror.master` || 'local'; chomp $gmm; return 'local' if $gmm eq 'local'; return 'master' if $gmm eq ( $GL_HOSTNAME || '' ); return "slave of $gmm"; } sub mirror_listslaves { my $repo = shift; return ( `git config --file $REPO_BASE/$repo.git/config --get gitolite.mirror.slaves` || '' ); } # is a redirect ok for this repo from this slave? sub mirror_redirectOK { my $repo = shift; my $slave = shift || return 0; # if we don't know who's asking, the answer is "no" my $gmrOK = `git config --file $REPO_BASE/$repo.git/config --get gitolite.mirror.redirectOK` || ''; chomp $gmrOK; my $slavelist = mirror_listslaves($repo); # if gmrOK is 'true', any valid slave can redirect return 1 if $gmrOK eq 'true' and $slavelist =~ /(^|\s)$slave(\s|$)/; # otherwise, gmrOK is a list of slaves who can redirect return 1 if $gmrOK =~ /(^|\s)$slave(\s|$)/; return 0; # LATER/NEVER: include a call to an external program to override a 'true', # based on, say, the time of day or network load etc. Cons: shelling out, # deciding the name of the program (yet another rc var?) } # ------------------------------------------------------------------------------ # per perl rules, this should be the last line in such a file: 1; sitaramc-gitolite-6857652/src/gitolite_env.pm000066400000000000000000000135751171610012500211650ustar00rootroot00000000000000# 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; sitaramc-gitolite-6857652/src/gitolite_rc.pm000066400000000000000000000071311171610012500207700ustar00rootroot00000000000000# 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 $GL_REF_OR_FILENAME_PATT $ADC_CMD_ARGS_PATT $BIG_INFO_CAP $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_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 $WEB_INTERFACE $GITWEB_URI_ESCAPE $REPO_BASE $REPO_UMASK $RSYNC_BASE $SVNSERVE $UPDATE_CHAINS_TO $AUTH_OPTIONS $GL_HOSTNAME $GL_HTTP_ANON_USER ); # ------------------------------------------------------------------------------ # real constants # ------------------------------------------------------------------------------ $current_data_version = '2.0'; $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._\@/,-]*$); # pattern for refnames pushed or names of files changed $GL_REF_OR_FILENAME_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._\@/+:-]*$); # maximum number of output lines from info under GL_BIG_CONFIG $BIG_INFO_CAP = 20; # default values for stuff that may be missing in the RC file $WEB_INTERFACE = 'gitweb'; # ------------------------------------------------------------------------------ # 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"; # fix up REPO_BASE $REPO_BASE = "$ENV{HOME}/$REPO_BASE" unless $REPO_BASE =~ m(^/); # backward incompat detection for mirroring. Normally I wouldn't do # this but this is *important* die "$ABRT Mirroring has completely changed in this version.\tYou need to check the documentation for how to upgrade\n" if (defined $GL_SLAVE_MODE or exists $ENV{GL_SLAVES}); # ------------------------------------------------------------------------------ # per perl rules, this should be the last line in such a file: 1; sitaramc-gitolite-6857652/src/gl-admin-push000077500000000000000000000025251171610012500205230ustar00rootroot00000000000000#!/bin/sh die() { echo "$@" >&2; exit 1; } # ---------- # if GL_BINDIR was not passed in, find it [ -z "$GL_BINDIR" ] && GL_BINDIR=` perl -ne 'print($1), exit if /^command="(.+?)\/gl-(time|auth-command) /' < $HOME/.ssh/authorized_keys` # GL_BINDIR still not known? we have a problem... [ -z "$GL_BINDIR" ] && { cat </dev/null` [ -z "$GL_RC" ] && die "hmm weird... GL_RC is undefined; ABORTING" GL_BINDIR=` get_rc_val GL_BINDIR ` GL_ADMINDIR=`get_rc_val GL_ADMINDIR` # and finally: git push "$@" sitaramc-gitolite-6857652/src/gl-auth-command000077500000000000000000000200071171610012500210260ustar00rootroot00000000000000#!/usr/bin/perl # ---------------------------------------------------------------------------- # 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)?' # - special gitolite commands: info, expand, (get|set)(perms|desc) # - other commands: anything in $GL_ADC_PATH if defined (see rc file) # # (smart) http mode # - started by apache (httpd) # - no arguments # - REQUEST_URI contains verb and repo, REMOTE_USER contains username # - REQUEST_URI looks like /path/reponame.git/(info/refs\?service=)?git-(receive|upload)-pack # - no special processing commands currently handled # ---------------------------------------------------------------------------- use strict; use warnings; # ---------------------------------------------------------------------------- # find the rc file, then pull the libraries in # ---------------------------------------------------------------------------- # 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; } # 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}; use gitolite_rc; # this does a "do" of the rc file use gitolite_env; use gitolite; # ---------------------------------------------------------------------------- # start... # ---------------------------------------------------------------------------- # 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 GL_USER and (if reqd) SSH_ORIGINAL_COMMAND and SSH_CONNECTION # ---------------------------------------------------------------------------- my $user; if ($ENV{REQUEST_URI}) { die "fallback to DAV not supported\n" if $ENV{REQUEST_METHOD} eq 'PROPFIND'; # 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(); $ENV{REMOTE_USER} ||= $GL_HTTP_ANON_USER; # see doc/http-backend.mkd $user = $ENV{GL_USER} = $ENV{REMOTE_USER}; } else { # no (more) arguments given in ssh mode? default user is $USER # (fedorahosted works like this, and it is harmless for others) @ARGV = ($ENV{USER}) unless @ARGV; $user = $ENV{GL_USER} = shift; } # ---------------------------------------------------------------------------- # SSH_ORIGINAL_COMMAND # ---------------------------------------------------------------------------- # no SSH_ORIGINAL_COMMAND given: shell out or default to 'info' unless ($ENV{SSH_ORIGINAL_COMMAND}) { shell_out() if $shell_allowed; # doesn't return ('exec's out) $ENV{SSH_ORIGINAL_COMMAND} = 'info'; } # quick sanity check for newlines; could be used to create fake log entries. # Not an access violation but possibly an audit/compliance reporting violation die "I don't like newlines in the command: $ENV{SSH_ORIGINAL_COMMAND}\n" if $ENV{SSH_ORIGINAL_COMMAND} =~ /[\n\r]/; # admin defined commands; please see doc/admin-defined-commands.mkd if ($GL_ADC_PATH and -d $GL_ADC_PATH) { try_adc(); # if it succeeds, this also 'exec's out } # 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; 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... # 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 # arg"). Currently, this is how git sends across the command (including the # single quotes): # git-receive-pack 'reponame.git' 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 ) { 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 =~ /\.\./; # save the reponame; too many things need this $ENV{GL_REPO}=$repo; # ---------------------------------------------------------------------------- # the real git commands (git-receive-pack, etc...) # ---------------------------------------------------------------------------- # we know the user and repo; we just need to know what perm he's trying for # (aa == attempted access; setting this makes some later logic simpler) my $aa = ($verb =~ $R_COMMANDS ? 'R' : 'W'); # writes may get redirected under certain conditions if ( $aa eq 'W' and mirror_mode($repo) =~ /^slave of (\S+)/ ) { my $master = $1; die "$ABRT GL_HOSTNAME not set; rejecting push to non-local repo\n" unless $GL_HOSTNAME; die "$ABRT $GL_HOSTNAME not the master, please push to $master\n" unless mirror_redirectOK($repo, $GL_HOSTNAME); print STDERR "$GL_HOSTNAME ==== $user ($repo) ===> $master\n"; exec("ssh", $master, "USER=$user", "SOC=$ENV{SSH_ORIGINAL_COMMAND}"); } # first level permissions check my ($perm, $creator, $wild); if ( $GL_ALL_READ_ALL and $verb =~ $R_COMMANDS and -d "$REPO_BASE/$repo.git") { $perm = 'R'; } else { ($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/); 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'; # run the pre-git hook if present (do this last, just before actually handing # off to git). Force its output to go to STDERR so the git client does not # get confused, in case the code in the pre-git hook forgot. To make it # simple for the script, send in $aa (which will be 'R' or 'W') so now they # have all three: GL_USER and GL_REPO in the env, and $aa as arg-1. if (-x "$REPO_BASE/$repo.git/hooks/gl-pre-git") { system("cd $REPO_BASE/$repo.git; hooks/gl-pre-git $aa >&2"); die "gl-pre-git hook failed ($?)\n" if $?; } # ---------------------------------------------------------------------------- # over to git now # ---------------------------------------------------------------------------- if ($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(); $repo = "'$REPO_BASE/$repo.git'"; exec("git", "shell", "-c", "$verb $repo") unless $verb eq 'git-init'; sitaramc-gitolite-6857652/src/gl-compile-conf000077500000000000000000000563601171610012500210370ustar00rootroot00000000000000#!/usr/bin/perl use strict; use warnings; 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 === # setup quiet mode if asked; please do not use this when running manually open STDOUT, ">", "/dev/null" if (@ARGV and shift eq '-q'); # ---------------------------------------------------------------------------- # definitions specific to this program # ---------------------------------------------------------------------------- # groups can now represent user groups or repo groups. # $groups{group}{member} = "master" (or name of fragment file in which the # group is defined). # our %groups = (); # moved to gitolite.pm now # %repos has two functions. # $repos{repo}{R|W}{user} = 1 if user has R (or W) permissions for at least # one branch in repo. This is used by the "level 1 check" (see faq). There's # also the new "C" (create a repo) permission now # $repos{repo}{user} is a list of {ref, perms} pairs. This is used by the # 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 :) # copy above desc to lite.pm -- my %repos = (); # 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 and config sequence numbers my $rule_seq = 0; my $config_seq = 0; # ... having been forced to use a list as described above, we lose some # efficiency due to the possibility of the same {ref, perms} pair showing up # multiple times for the same repo+user. So... my %rurp_seen = (); # catch usernames<->pubkeys mismatches; search for "lint" below my %user_list = (); # repo specific 'git config' stuff # our %git_configs = (); # moved to gitolite.pm now # gitweb descriptions and owners; plain text, keyed by "$repo.git" my %desc = (); my %owner = (); # backward compat for delegation my $subconf_seen = 0; # ---------------------------------------------------------------------------- # subroutines # ---------------------------------------------------------------------------- sub expand_list { my @list = @_; my @new_list = (); for my $item (@list) { if ($item =~ /^@/ and $item ne '@all') # nested group { die "$ABRT undefined group $item\n" unless $groups{$item}; # add those names to the list push @new_list, sort keys %{ $groups{$item} }; } else { push @new_list, $item; } } return @new_list; } sub device_inode { my $file = shift; return join("/", (stat $file)[0,1]); } # ---------------------------------------------------------------------------- # "compile" GL conf # ---------------------------------------------------------------------------- # detect recursion in include files; see processing of "include" statement later my %included; $included{device_inode("conf/gitolite.conf")}++; my %prefixed_groupname = (); sub check_fragment_repo_disallowed { # trying to set access for $repo (='foo')... my ($fragment, $repo) = @_; # processing the master config, not a fragment return 0 if $fragment eq 'master'; # fragment is also called 'foo' (you're allowed to have a # fragment that is only concerned with one repo) return 0 if $fragment eq $repo; # same thing in big-config-land; foo is just @foo now return 0 if $GL_BIG_CONFIG and ("\@$fragment" eq $repo); my @matched = grep { $repo =~ /^$_$/ } grep { $groups{"\@$fragment"}{$_} eq 'master' } sort keys %{ $groups{"\@$fragment"} }; return 0 if @matched > 0; return 1; } sub parse_conf_line { my ($line, $fragment, $repos_p, $ignored_p) = @_; # user or repo groups if ($line =~ /^(@\S+) = ?(.*)/) { # store the members of each group as hash key. Keep track of when # the group was *first* created by using $fragment as the *value* do { $groups{$1}{$_} ||= $fragment } for ( expand_list( split(' ', $2) ) ); # create the group hash even if empty $groups{$1} = {} unless $groups{$1}; die "$ABRT bad group '$1'\n" unless $1 =~ $REPONAME_PATT; } # repo(s) elsif ($line =~ /^repo (.*)/) { # grab the list... @{ $repos_p } = split ' ', $1; # ...expand groups in the default case @{ $repos_p } = expand_list ( @{ $repos_p } ) unless $GL_BIG_CONFIG; # ...sanity check for (@{ $repos_p }) { print STDERR "$WARN explicit '.git' extension ignored for $_.git\n" if s/\.git$//; die "$ABRT bad reponame '$_'\n" if ($GL_WILDREPOS and $_ !~ $REPOPATT_PATT); die "$ABRT bad reponame '$_' or you forgot to set \$GL_WILDREPOS\n" if (not $GL_WILDREPOS and $_ !~ $REPONAME_PATT); } s/\bCREAT[EO]R\b/\$creator/g for @{ $repos_p }; } # actual permission line elsif ($line =~ /^(-|C|R|RW\+?(?:C?D?|D?C?)M?) (.* )?= (.+)/) { my $perms = $1; my @refs; @refs = split( ' ', $2 ) if $2; @refs = expand_list ( @refs ); my @users = split ' ', $3; die "$ABRT \$GL_WILDREPOS is not set, you cant use 'C' in config\n" if $perms eq 'C' and not $GL_WILDREPOS; # if no ref is given, this PERM applies to all refs @refs = qw(refs/.*) unless @refs; # deprecation warning map { print STDERR "WARNING: old syntax 'PATH/' found; please use new syntax 'NAME/'\n" if s(^PATH/)(NAME/) } @refs; # fully qualify refs that dont start with "refs/" or "NAME/"; # prefix them with "refs/heads/" @refs = map { m(^(refs|NAME)/) or s(^)(refs/heads/); $_ } @refs; @refs = map { s(/USER/)(/\$gl_user/); $_ } @refs; # expand the user list, unless it is just "@all" @users = expand_list ( @users ) unless $GL_BIG_CONFIG; do { die "$ABRT bad username '$_'\n" unless $_ =~ $USERNAME_PATT } for @users; s/\bCREAT[EO]R\b/~\$creator/g for @users; # ok, we can finally populate the %repos hash for my $repo (@{ $repos_p }) # each repo in the current stanza { # if we're processing a delegated config file (not the master # config), we need to prevent attempts by that admin to obtain # rights on stuff outside his domain # trying to set access for $repo (='foo')... if (check_fragment_repo_disallowed( $fragment, $repo )) { my $repo = $repo; $repo =~ s/^\@$fragment\./locally modified \@/; $ignored_p->{$fragment}{$repo} = 1; next; } for my $user (@users) { # lint check, to catch pubkey/username typos if ($user =~ /^@/ and $user ne '@all') { # this is a usergroup, not a normal user; happens with GL_BIG_CONFIG if (exists $groups{$user}) { $user_list{$_}++ for keys %{ $groups{$user} }; } } else { $user_list{$user}++; } # for 1st level check (see faq/tips doc) $repos{$repo}{C}{$user} = 1, next if $perms eq 'C'; $repos{$repo}{R}{$user} = 1 if $perms =~ /R/; $repos{$repo}{W}{$user} = 1 if $perms =~ /W|D/; # if the user specified even a single 'D' anywhere, make # that fact easy to find; this changes the meaning of RW+ # to no longer permit deletes (see update hook) $repos{$repo}{DELETE_IS_D} = 1 if $perms =~ /D/; $repos{$repo}{CREATE_IS_C} = 1 if $perms =~ /RW.*C/; $repos{$repo}{MERGE_CHECK} = 1 if $perms =~ /M/; # for 2nd level check, store each "ref, perms" pair in order for my $ref (@refs) { # checking NAME based restrictions is expensive for # the update hook (see the changes to src/hooks/update # in this commit for why) so we would *very* much like # to avoid doing it for the large majority of repos # that do *not* use NAME limits. Setting a flag that # can be checked right away will help us do that $repos{$repo}{NAME_LIMITS} = 1 if $ref =~ /^NAME\//; my $p_user = $user; $p_user =~ s/creator$/creator - wild/; push @{ $repos{$repo}{$p_user} }, [ $rule_seq++, $ref, $perms ] unless $rurp_seen{$repo}{$p_user}{$ref}{$perms}++; } } } } # repo specific 'git config' stuff elsif ($line =~ /^config (.+) = ?(.*)/) { my ($key, $value) = ($1, $2); my @validkeys = split(' ', ($GL_GITCONFIG_KEYS || '') ); push @validkeys, "gitolite-options\\..*"; my @matched = grep { $key =~ /^$_$/ } @validkeys; die "$ABRT git config $key not allowed\ncheck GL_GITCONFIG_KEYS in the rc file for how to allow it\n" if (@matched < 1); for my $repo (@{ $repos_p }) # each repo in the current stanza { $git_configs{$repo}{$config_seq++}{$key} = $value; # force entry in %repos. Without this, a repo para with just a # config line and no ACLs gets ignored in the output $repos{$repo}{HAS_CONFIG} = 1; } } # include and subconf. subconf is just a special case of "include", # saying that the config parse should "switch" contexts elsif ($line =~ /^(include|subconf) "(.+)"/) { my $include_glob = $2; my $subconf = ( $1 eq 'subconf' ); die "$ABRT subconf $fragment attempting to run 'subconf'\n" if $subconf and $fragment ne 'master'; # substitute HOSTNAME word if GL_HOSTNAME defined, otherwise leave as is $include_glob =~ s/\bHOSTNAME\b/$GL_HOSTNAME/ if $GL_HOSTNAME; for my $file (glob($include_glob =~ m(^/) ? $include_glob : "$GL_ADMINDIR/conf/$include_glob")) { warn("$WARN included file not found: '$file'\n"), next unless -f $file; my $file_id = device_inode($file); warn("$WARN $file already included\n"), next if ($included{$file_id}++); if ($subconf) { die "$ABRT subconf filename should end in .conf\n" unless $file =~ /^.*\/(.*).conf$/; parse_conf_file( $file, $1 ); $subconf_seen++; } else { parse_conf_file( $file, $fragment ); } } } # very simple syntax for the gitweb description of repo; one of: # reponame = "some description string" # reponame "owner name" = "some description string" elsif ($line =~ /^(\S+)(?: "(.*?)")? = "(.*)"$/) { my ($repo, $owner, $desc) = ($1, $2, $3); die "$ABRT bad repo name '$repo'\n" unless $repo =~ $REPONAME_PATT; die "$ABRT $fragment attempting to set description for $repo\n" if check_fragment_repo_disallowed( $fragment, $repo ); $desc{"$repo.git"} = $desc; $owner{"$repo.git"} = $owner || ''; } else { die "$ABRT can't make head or tail of '$line'\n"; } } sub cleanup_conf_line { my ($line) = @_; # kill comments, but take care of "#" inside *simple* strings $line =~ s/^((".*?"|[^#"])*)#.*/$1/; # normalise whitespace; keeps later regexes very simple $line =~ s/=/ = /; $line =~ s/\s+/ /g; $line =~ s/^ //; $line =~ s/ $//; return $line; } sub parse_conf_file { my ($conffile, $fragment) = @_; # the second arg, $fragment, is passed in as "master" when parsing the # main config, and the fragment name when parsing a fragment. In the # latter case, the parser uses that information to ignore (and warn about) # any repos in the fragment that are not members of the "repo group" of # the same name. my %ignored = (); my $conf_fh = wrap_open( "<", $conffile ); # the syntax is fairly simple, so we parse it inline my @repos; my $line; while (<$conf_fh>) { $line = cleanup_conf_line($_); # skip blank lines next unless $line =~ /\S/; # this is how we prevent subconf hacking; we internally prefix all # group names *defined* in the subconf (also if they are later used) # with the subconf name. # rules for prefixing the subconf name: prefix it if the @group name # has appeared earlier in this file on the *left side*. Prefix all # left side @group names regardless. if ($fragment ne 'master') { my $lhs = ''; # save 'foo' if it's an '@foo = list' line $lhs = $1 if $line =~ /^@(\S+) = /; # prefix all @group in the line $line =~ s/(^| )(@\S+)(?= |$)/ $1 . ($prefixed_groupname{$fragment}{$2} || $2) /ge; # now prefix the LHS and store it if needed if ($lhs) { $line =~ s/^@\S+ = /"\@$fragment.$lhs = "/e; $prefixed_groupname{$fragment}{"\@$lhs"} = "\@$fragment.$lhs"; } } parse_conf_line( $line, $fragment, \@repos, \%ignored ); } # backward compat for delegation parse_conf_line( 'subconf "fragments/*.conf"', $fragment, \@repos, \%ignored ) if ($conffile eq $GL_CONF and $fragment eq 'master' and not $subconf_seen); for my $ig (sort keys %ignored) { warn "\n\t\t***** WARNING *****\n" . "\t$ig.conf attempting to set access for " . join (", ", sort keys %{ $ignored{$ig} }) . "\n"; } } # parse the main config file parse_conf_file($GL_CONF, 'master'); # ---------------------------------------------------------------------------- # (that ends the config file compiler, though we postpone the writing # for now to deal with the latest GL_BIG_CONFIG innovation!) # ---------------------------------------------------------------------------- # ---------------------------------------------------------------------------- # what's the git version? # ---------------------------------------------------------------------------- # we don't like stuff older than 1.6.6 my $git_version = `git --version`; die " *** ERROR *** did not get a proper version number. Please see if git is in the PATH on the server. If it is not, please edit ~/.gitolite.rc on the server and set the \$GIT_PATH variable to the correct value\n " unless $git_version; my ($gv_maj, $gv_min, $gv_patchrel) = ($git_version =~ m/git version (\d+)\.(\d+)\.(\d+)/); die "$ABRT I can't understand $git_version\n" unless ($gv_maj >= 1); $git_version = $gv_maj*10000 + $gv_min*100 + $gv_patchrel; # now it's "normalised" die "\n\t\t***** AAARGH! *****\n" . "\tyour git version is older than 1.6.6\n" . "\tsince that is now more than one year old, and gitolite needs some of\n" . "\tthe newer features, please upgrade.\n" if $git_version < 10606; # that's 1.6.6 to you # ---------------------------------------------------------------------------- # most of the rest of this program can be "switched off"; see # doc/big-config.mkd for details. # ---------------------------------------------------------------------------- # process the normal repos in %repos. This includes creating them if needed # (and GL_NO_CREATE_REPOS is not set), checking hooks, and finally, if # GL_BIG_CONFIG is set, writing out the one-repo config file for directly # specified repos (i.e., "repo foo", not "@grp = foo" + "repo @grp") do_normal_repos(); write_compiled_conf(); # write out the final compiled config # ---------------------------------------------------------------------------- # process the normal repos in %repos (create, hook, one_repo config...) # ---------------------------------------------------------------------------- sub do_normal_repos { wrap_chdir($REPO_BASE); # start with the ones that are normal repos in %repos my @repos = grep { $_ =~ $REPONAME_PATT and not /^@/ } sort keys %repos; # then, for each repogroup, find the members of the group and add them in map { push @repos, keys %{ $groups{$_} } } grep { /^@/ } keys %repos; # weed out duplicates (the code in the loop below is disk activity!) my %seen = map { $_ => 1 } @repos; @repos = sort keys %seen; for my $repo (sort @repos) { next unless $repo =~ $REPONAME_PATT; # skip repo patterns next if $repo =~ m(^\@|EXTCMD/); # skip groups and fake repos unless ($GL_NO_CREATE_REPOS) { unless (-d "$repo.git") { print STDERR "creating $repo...\n"; new_repo($repo, "$GL_ADMINDIR/hooks/common"); # new_repo would have chdir'd us away; come back wrap_chdir($REPO_BASE); } # detect repos copied from elsewhere by absence of (empty) # sentinel file, and if it doesn't exist run the ln -sf unless (-l "$repo.git/hooks/gitolite-hooked") { ln_sf("$GL_ADMINDIR/hooks/common", "*", "$repo.git/hooks"); # in case of package install, GL_ADMINDIR is no longer the top cop; # override with the package hooks ln_sf("$GL_PACKAGE_HOOKS/common", "*", "$repo.git/hooks") if $GL_PACKAGE_HOOKS; } } # write a one_repo config for normal repos declared directly (not just via a group) write_1_compiled_conf($repo) if $GL_BIG_CONFIG and $repos{$repo} and -d "$repo.git"; } } sub write_1_compiled_conf { # warning: writes and *deletes* it from %repos and %git_configs my ($repo) = shift; my (%one_repo, %one_git_config); open(my $compiled_fh, ">", "$repo.git/gl-conf") or return; $one_repo{$repo} = $repos{$repo}; delete $repos{$repo}; my $dumped_data = Data::Dumper->Dump([\%one_repo], [qw(*one_repo)]); if ($git_configs{$repo}) { $one_git_config{$repo} = $git_configs{$repo}; delete $git_configs{$repo}; $dumped_data .= Data::Dumper->Dump([\%one_git_config], [qw(*one_git_config)]); } # 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; close $compiled_fh; $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; # 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 # stuff to all of them is skipped if this variable is set. This is primarily # for the Fedora folks, but it should be useful for anyone who has a huge set # of repos and wants to manage gitweb/daemon/etc access via other means (they # typically have the whole thing controlled by a web-app and a database # anyway, and gitolite is only doing the access control and nothing more). # ---------------------------------------------------------------------------- # various updates to all real repos # ---------------------------------------------------------------------------- # ---------------------------------------------------------------------------- # update repo configurations, gitweb description, daemon export-ok, etc # ---------------------------------------------------------------------------- my %projlist = (); # for each real repo (and remember this will be empty, thus skipping all this, # if $GL_NO_DAEMON_NO_GITWEB is on!) # note: we do them in 2 separate loops to avoid breaking the optimisation in # sub parse_acl (look for variable $saved_crwu) for my $repo (@phy_repos) { wrap_chdir("$REPO_BASE/$repo.git"); # daemon is easy setup_daemon_access($repo); } for my $repo (@phy_repos) { wrap_chdir("$REPO_BASE/$repo.git"); # gitweb is a little more complicated. Here're some notes: # - "setup_gitweb_access" also sets "owner", despite the name # - specifying a description also counts as enabling gitweb # - description and owner are not specified for wildrepos; they're # specified for *actual* repos, even if the repo was created by a # wild card spec and "C" permissions. If you see the docs for the # gitolite.conf file, you will see that repo owner/desc don't go # 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"} || ''); # git config # implementation note: this must happen *after* one of the previous 2 # calls (setup daemon or gitweb). The reason is that they call # "can_read", which eventually calls parse_acl with the right "creator" # 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}; } # write out the project list, but not if GL_NO_DAEMON_NO_GITWEB is set unless ($GL_NO_DAEMON_NO_GITWEB) { setup_web_access(\%projlist); } # ---------------------------------------------------------------------------- # "compile" ssh authorized_keys # ---------------------------------------------------------------------------- unless ($GL_NO_SETUP_AUTHKEYS) { setup_authkeys($GL_KEYDIR, \%user_list); } sitaramc-gitolite-6857652/src/gl-conf-convert000077500000000000000000000065041171610012500210620ustar00rootroot00000000000000#!/usr/bin/perl -w # # migrate gitosis.conf to gitolite.conf format # # Based on gl-conf-convert by: Sitaram Chamarty # Rewritten by: Behan Webster # use strict; use warnings; my @comments = (); my $groupname; my %groups; my $reponame; my %repos; while (<>) { # not supported if (/^repositories *=/ or /^map /) { print STDERR "not supported: $_"; s/^/NOT SUPPORTED: /; print; next; } # normalise whitespace to help later regexes chomp; s/\s+/ /g; s/ ?= ?/ = /; s/^ //; s/ $//; if (/^\s*$/ and @comments > 1) { @{$repos{$reponame}{comments}} = @comments if $reponame; @{$groups{$groupname}{comments}} = @comments if $groupname; @comments = (); } elsif (/^\s*#/) { push @comments, $_; } elsif (/^\[repo\s+(.*?)\]$/) { $groupname = ''; $reponame = $1; $reponame =~ s/\.git$//; } elsif (/^gitweb\s*=\s*yes/i) { push @{$repos{$reponame}{R}}, 'gitweb'; } elsif (/^daemon\s*=\s*yes/i) { push @{$repos{$reponame}{R}}, 'daemon'; } elsif (/^description\s*=\s*(.+?)$/) { $repos{$reponame}{desc} = $1; } elsif (/^owner\s*=\s*(.+?)$/) { $repos{$reponame}{owner} = $1; } elsif (/^\[group\s+(.*)\]$/) { $reponame = ''; $groupname = $1; } elsif (/^members\s*=\s*(.*)/) { push @{$groups{$groupname}{users}}, map {s/\@([^.]+)$/_$1/g; $_} split(' ', $1); } elsif (/^write?able\s*=\s*(.*)/) { foreach my $repo (split(' ', $1)) { $repo =~ s/\.git$//; push @{$repos{$repo}{RW}}, "\@$groupname"; } } elsif (/^readonly\s*=\s*(.*)/) { foreach my $repo (split(' ', $1)) { $repo =~ s/\.git$//; push @{$repos{$repo}{R}}, "\@$groupname"; } } } #use Data::Dumper; #print Dumper(\%repos); #print Dumper(\%groups); # Groups print "#\n# Groups\n#\n\n"; foreach my $grp (sort keys %groups) { next unless @{$groups{$grp}{users}}; printf join("\n", @{$groups{$grp}{comments}})."\n" if $groups{$grp}{comments}; printf "\@%-19s = %s\n", $grp, join(' ', @{$groups{$grp}{users}}); } # Gitweb print "\n#\n# Gitweb\n#\n\n"; foreach my $repo (sort keys %repos) { if ($repos{$repo}{desc}) { @{$repos{$repo}{R}} = grep(!/^gitweb$/, @{$repos{$repo}{R}}); print $repo; print " \"$repos{$repo}{owner}\"" if $repos{$repo}{owner}; print " = \"$repos{$repo}{desc}\"\n"; } } # Repos print "\n#\n# Repos\n#\n"; foreach my $repo (sort keys %repos) { print "\n"; printf join("\n", @{$repos{$repo}{comments}})."\n" if $repos{$repo}{comments}; #if ($repos{$repo}{desc}) { # @{$repos{$repo}{R}} = grep(!/^gitweb$/, @{$repos{$repo}{R}}); #} print "repo\t$repo\n"; foreach my $access (qw(RW+ RW R)) { next unless $repos{$repo}{$access}; my @keys; foreach my $key (@{$repos{$repo}{$access}}) { if ($key =~ /^\@(.*)/) { next unless defined $groups{$1} and @{$groups{$1}{users}}; } push @keys, $key; } printf "\t$access\t= %s\n", join(' ', @keys) if @keys; } #if ($repos{$repo}{desc}) { # print $repo; # print " \"$repos{$repo}{owner}\"" if $repos{$repo}{owner}; # print " = \"$repos{$repo}{desc}\"\n"; #} } sitaramc-gitolite-6857652/src/gl-dryrun000077500000000000000000000065001171610012500177760ustar00rootroot00000000000000#!/bin/bash die() { echo; echo "FATAL: $@"; usage; } usage() { cat < .gitolite/conf/VERSION # setup a minimal .gitolite.rc export GL_RC=$PWD/gl_rc cat > $GL_RC <<'EOF' $GL_ADMINDIR=$ENV{PWD} . "/.gitolite"; $GL_CONF="$GL_ADMINDIR/conf/gitolite.conf"; $GL_KEYDIR="$GL_ADMINDIR/keydir"; $GL_CONF_COMPILED="$GL_ADMINDIR/conf/gitolite.conf-compiled.pm"; $GL_WILDREPOS = 1; $PROJECTS_LIST = $ENV{PWD} . "/projects.list"; $REPO_UMASK = 0077; $GL_BIG_CONFIG = 0; $GL_NO_DAEMON_NO_GITWEB = 1; $GIT_PATH=""; $GL_GITCONFIG_KEYS = ".*"; $GL_NO_CREATE_REPOS = 1; $GL_NO_SETUP_AUTHKEYS = 1; $HTPASSWD_FILE = ""; $RSYNC_BASE = ""; $SVNSERVE = ""; $GL_WILDREPOS_PERM_CATS = "READERS WRITERS"; $GL_LOGT="$GL_ADMINDIR/logs/gitolite-%y-%m.log"; $REPO_BASE="repositories"; 1; EOF # now compile it echo compiling... $GL_BINDIR/gl-compile-conf echo echo "checking if $admin has push rights..." SSH_ORIGINAL_COMMAND=info $GL_BINDIR/gl-auth-command $admin echo # now find out who has admin... echo "checking what pubkeys (if any) have push rights..." for f in `find .gitolite/keydir -name "*.pub" | sort` do f=`basename $f` u=`perl -e '$u = shift; $u =~ s/(\@[^.]+)?\.pub$//; print $u' $f` SSH_ORIGINAL_COMMAND=info $GL_BINDIR/gl-auth-command $u | grep R...W..gitolite-admin.$ > /dev/null && echo ' '$f done echo # now look for duplicate keys etc echo "looking for (duplicate) pubkeys; they could cause later ones to be 'hidden'..." cd .gitolite for f in `find keydir -name "*.pub" | sort` do ssh-keygen -l -f "$f" done | perl -ane ' warn " $F[2] is hidden by $seen{$F[1]}\n" if $seen{$F[1]}; $seen{$F[1]} = $F[2]; ' cd .. echo echo ...done sitaramc-gitolite-6857652/src/gl-install000077500000000000000000000104051171610012500201200ustar00rootroot00000000000000#!/usr/bin/perl # INTERNAL COMMAND. NOT MEANT TO BE RUN BY THE USER DIRECTLY. 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... # ---------------------------------------------------------------------------- # setup quiet mode if asked; please do not use this when running manually open STDOUT, ">", "/dev/null" if (@ARGV and shift eq '-q'); # wrapper around mkdir; it's not an error if the directory exists, but it is # an error if it doesn't exist and we can't create it sub wrap_mkdir { my $dir = shift; my $perm = shift; # optional if ( -d $dir ) { print "$dir already exists\n"; return; } mkdir($dir) or die "mkdir $dir failed: $!\n"; chmod $perm, $dir if $perm; print "created $dir\n"; } 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 $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"; exit; } # add a custom path for git binaries, if specified $ENV{PATH} .= ":$GIT_PATH" if $GIT_PATH; # set the umask before creating any files/directories umask($REPO_UMASK); # mkdir $REPO_BASE, $GL_ADMINDIR if they don't already exist wrap_mkdir($REPO_BASE); wrap_mkdir($GL_ADMINDIR, 0700); # mkdir $GL_ADMINDIR's subdirs for my $dir (qw(conf doc keydir logs src hooks hooks/common hooks/gitolite-admin)) { # some of them will stay empty; too lazy to fix right now ;-) wrap_mkdir("$GL_ADMINDIR/$dir", 0700); } # "src" and "doc" will be overwritten on each install, but not conf if ($GL_PACKAGE_HOOKS) { system("cp -R -p $GL_PACKAGE_HOOKS $GL_ADMINDIR"); } else { 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) { print <&2; exit 1; } get_rc_val() { `dirname $0`/gl-query-rc $1; } # ---------- # is mirroring even enabled? hn=`get_rc_val GL_HOSTNAME` [ -z "$hn" ] && exit # we should not be invoked directly from the command line [ -z "$GL_LOG" ] && die fatal: do not run $0 directly # ---------- # get repo name then check if it's a local or slave (ie we're not the master) [ -z "$1" ] && die fatal: missing reponame argument repo=$1; shift REPO_BASE=`get_rc_val REPO_BASE` cd $REPO_BASE/$repo.git 2>/dev/null || die fatal: could not change directory to "$repo" gmm=`git config --get gitolite.mirror.master` # is it local? (remember, empty/undef ==> local gmm=${gmm:-local} [ "$gmm" = "local" ] && die sorry but "$repo" is local # is it a slave? [ "$hn" = "$gmm" ] || die fatal: wrong master. Try $gmm... # ---------- # now see if we want to be foregrounded. Fg mode accepts only one slave [ "$1" = "-fg" ] && { [ -z "$2" ] && die fatal: missing slavename argument [ -n "$3" ] && die fatal: too many slavenames git push --mirror $2:$repo 2>&1 | sed -e "s/^/$hn:/" exit } # ---------- # normal (self-backgrounding) mode, one or more slaves [ -z "$1" ] && die fatal: missing list of slaves export slaves slaves="$*" # ---------- # print out the job ID, then redirect all 3 FDs export job_id=$$ # can change to something else if needed echo "($job_id&) $hn ==== ($repo) ===>" $slaves >&2 logfile=`echo $GL_LOG | sed 's/\.log$/-mirror-pushes.log/'` exec >>$logfile 2>&1 ' for s in $slaves do [ "$s" = "$hn" ] && continue # skip ourselves git push --mirror $s:$repo || echo ==== WARNING: RC=$? from git push --mirror $s:$repo ==== done 2>&1 | sed -e "s/^/ /" echo `date +%F.%T` '===>' $slaves echo ) 2>&1 | sed -e "s/^/$job_id:/" & # background the whole thing ) sitaramc-gitolite-6857652/src/gl-mirror-shell000077500000000000000000000137021171610012500210740ustar00rootroot00000000000000#!/usr/bin/perl # terminology: # native repo: a repo for which we are the master; pushes happen here # authkeys: shorthand for ~/.ssh/authorized_keys # this is invoked in one of two ways: # (1) locally, from a shell script or command line # (2) from a remote server, via authkeys, with one argument (the name of the # sending server), similar to what happens with normal users and the # 'gl-auth-command' program. SSH_ORIGINAL_COMMAND will then contain the # actual command that the remote sent. # # Currently, these commands are (a) 'info', (b) 'git-receive-pack' when a # mirror push is *received* by a slave, (c) 'request-push' sent by a slave # (possibly via an ADC) when the slave finds itself out of sync, (d) a # redirected push, from a user pushing to a slave, which is represented not by # a command per se but by starting with "USER=..." use strict; use warnings; # ---------------------------------------------------------------------------- # this section of code snarfed from gl-auth-command BEGIN { $0 =~ m|^(/)?(.*)/| and $ENV{GL_BINDIR} = ($1 || "$ENV{PWD}/") . $2; } use lib $ENV{GL_BINDIR}; use gitolite_rc; use gitolite_env; use gitolite; setup_environment(); die "fatal: GL_HOSTNAME not set in rc; mirroring disabled\n" unless $GL_HOSTNAME; # ---------------------------------------------------------------------------- # deal with local invocations first # on the "master", run from a shell, for one specific repo, with an optional # list of slaves, like so: # gl-mirror-shell request-push some-repo [optional list of slaves/keys] if ( ($ARGV[0] || '') eq 'request-push' and not $ENV{SSH_ORIGINAL_COMMAND} ) { shift; my $repo = shift or die "fatal: missing reponame\n"; -d "$REPO_BASE/$repo.git" or die "fatal: no such repo?\n"; # this is the default argument if no slave list or key is supplied @ARGV = ('gitolite.mirror.slaves') unless @ARGV; my @slaves = (); my %seen = (); # each argument in @ARGV is either a slave name, or a gitolite mirroring # key to be replaced with its value, split into a list of slaves while (@ARGV) { $a = shift @ARGV; if ($a =~ /^gitolite\.mirror\.[\w.-]+$/) { my @values = split(' ', `git config --file $REPO_BASE/$repo.git/config --get $a` || ''); unshift @ARGV, @values; } else { push @slaves, $a unless $seen{$a}++; } } exit 1 unless @slaves; # we don't want to complain louder than that because the most common # use of this script on the master server is via cron, run against # *all* known repos without checking their individual key values print STDERR "info: mirror-push $repo ", join(" ", @slaves), "\n"; system("gl-mirror-push", $repo, @slaves); exit 0; } unless (@ARGV) { print STDERR "fatal: missing command\n"; exit 1; } # ---------- # now the remote invocations; log it, then get the sender name my $sender = shift; $ENV{GL_USER} ||= "host:$sender"; # default SSH_ORIGINAL_COMMAND is 'info', as usual $ENV{SSH_ORIGINAL_COMMAND} ||= 'info'; # and it's too long to bloody type... my $soc = $ENV{SSH_ORIGINAL_COMMAND}; log_it(); # ---------- # our famous 'info' command if ($soc eq 'info') { print STDERR "Hello $sender, I am $GL_HOSTNAME\n"; exit; } # ---------- # when running on the "slave", we have to "receive" the `git push --mirror` # from a master. Check that the repo is indeed a slave and the sender is the # correct master before allowing the push. if ($soc =~ /^git-receive-pack '(\S+)'$/) { my $repo = $1; die "fatal: invalid characters in $repo\n" unless $repo =~ $REPONAME_PATT; my $mm = mirror_mode($repo); # reminder: we're not going through the slave-side gl-auth-command. This # is a server-to-server transaction, with an authenticated sender. # Authorisation consists of checking to make sure our config says this # sender is indeed the master for this repo die "$ABRT fatal: $GL_HOSTNAME <==//== $sender mirror-push rejected: $repo is $mm\n" unless $mm eq "slave of $sender"; print STDERR "$GL_HOSTNAME <=== ($repo) ==== $sender\n"; $ENV{GL_BYPASS_UPDATE_HOOK} = 1; $ENV{GL_REPO} = $repo; # replace the repo path with the full path and hand off to git-shell $soc =~ s(')('$ENV{GL_REPO_BASE_ABS}/); exec("git", "shell", "-c", $soc); } # ---------- # a slave may have found itself out of sync (perhaps the network was down at # the time of the last push to the master), and now wants to request a sync. # This is similar to the "local invocation" described above, but we check the # sender name against gitolite.mirror.slaves to prevent some random slave from # asking for a repo it should not be having! if ($soc =~ /^request-push (\S+)$/) { my $repo = $1; die "fatal: invalid characters in $repo\n" unless $repo =~ $REPONAME_PATT; die "$ABRT fatal: $GL_HOSTNAME ==//==> $sender refused: not in slave list\n" unless mirror_listslaves($repo) =~ /(^|\s)$sender(\s|$)/; print STDERR "$GL_HOSTNAME ==== ($repo) ===> $sender\n"; # just one sender, and we've checked that he is "on the list". Foreground... system("$ENV{GL_BINDIR}/gl-mirror-push", $repo, "-fg", $sender); exit; } # ---------- # experimental feature... # when running on the "master", receive a redirected push from a slave. This # is disabled by default and needs to be explicitly enabled on both the master # and the slave. SEE DOCUMENTATION FOR CAVEATS AND CAUTIONS. if ($soc =~ /^USER=(\S+) SOC=(git-receive-pack '(\S+)')$/) { my $user = $1; $ENV{SSH_ORIGINAL_COMMAND} = $2; my $repo = $3; die "fatal: invalid characters in $user\n" unless $user =~ $USERNAME_PATT; die "fatal: invalid characters in $repo\n" unless $repo =~ $REPONAME_PATT; die "$ABRT fatal: $GL_HOSTNAME <==//== $sender redirected push rejected\n" unless mirror_redirectOK($repo, $sender); print STDERR "$GL_HOSTNAME <=== $user ($repo) ==== $sender\n"; my $pgm = $0; $pgm =~ s([^/]+$)(gl-auth-command); exec($pgm, $user); } sitaramc-gitolite-6857652/src/gl-query-rc000077500000000000000000000011221171610012500202150ustar00rootroot00000000000000#!/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; sitaramc-gitolite-6857652/src/gl-setup000077500000000000000000000106041171610012500176130ustar00rootroot00000000000000#!/bin/sh GL_PACKAGE_CONF=/tmp/share/gitolite/conf # must be the same as the value for the same variable in # $GL_PACKAGE_CONF/example.gitolite.rc. Sorry about the catch-22 :) # TODO need to fix for portability to ksh and so on # TODO need to get the version in there somehow # This program is meant to be completely non-interactive, suitable for running # server-side from a "post RPM/DEB install" script, or manually by users. # usage: # $0 [foo.pub] # The pubkey filename must end with ".pub" and is mandatory when you first run # this command. Otherwise it is optional, and can be used to override a # pubkey file if you happen to have lost all gitolite-access to the repos (but # do have shell access via some other means) die() { echo "$@" >&2; exit 1; } get_rc_val() { `dirname $0`/gl-query-rc $1 } TEMPDIR=`mktemp -d -t tmp.XXXXXXXXXX` export TEMPDIR trap "/bin/rm -rf $TEMPDIR" 0 # quiet mode; only used to suppress popping up an editor on a new rc file if [ "$1" = "-q" ] then shift quiet=1 fi if [ -n "$GITOLITE_HTTP_HOME" ] then HOME=$GITOLITE_HTTP_HOME admin_name=$1 else pubkey_file=$1 admin_name= if [ -n "$pubkey_file" ] then echo $pubkey_file | grep '.pub$' >/dev/null || die "$pubkey_file must end in .pub" [ -f $pubkey_file ] || die "cant find $pubkey_file" admin_name=` basename $pubkey_file .pub` echo $admin_name | grep '@' >/dev/null && die "please don't use '@' in the initial admin name" fi fi 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 $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 $GL_RC manually if you need features echo controlled by any of the following variables: echo ---- sed -e 's/^/ /' < $TEMPDIR/.diffvars echo ---- fi 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 $GL_RC if [ -z "$quiet" ] then 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} $GL_RC fi fi # setup ssh stuff. We break our normal rule that we will not fiddle with # authkeys etc., because in this case it seems appropriate ( cd $HOME mkdir -p .ssh chmod go-rwx .ssh touch .ssh/authorized_keys 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 [ -f $GL_ADMINDIR/conf/gitolite.conf ] || { cat < $GL_ADMINDIR/conf/gitolite.conf repo gitolite-admin RW+ = $admin_name repo testing RW+ = @all EOF } [ -n "$pubkey_file" ] && cp $pubkey_file $GL_ADMINDIR/keydir touch $HOME/.ssh/authorized_keys gl-compile-conf -q # setup push-to-admin [ -n "$pubkey_file" ] && ( cd $HOME; cd $REPO_BASE/gitolite-admin.git GIT_WORK_TREE=$GL_ADMINDIR; export GIT_WORK_TREE git add conf/gitolite.conf keydir git config --get user.email >/dev/null || git config user.email $USER@`hostname` git config --get user.name >/dev/null || git config user.name "$USER on `hostname`" git diff --cached --quiet 2>/dev/null || git commit -am start ) # now that the admin repo is created, you have to set the hooks properly; best # do it by running install again gl-install -q # ---- # the never-ending quest to help with bloody ssh issues... cd $GL_ADMINDIR/keydir [ -n "$pubkey_file" ] && $GL_BINDIR/sshkeys-lint -q -a $admin_name < $HOME/.ssh/authorized_keys exit 0 sitaramc-gitolite-6857652/src/gl-setup-authkeys000077500000000000000000000032731171610012500214520ustar00rootroot00000000000000#!/usr/bin/perl -w # documentation for this program is right here, please read # IMPORTANT: also see usage notes below # BACKGROUND/PURPOSE: # - an external program populates "keydir" with *all* keys and then # calls this program, giving "keydir" as arg-1 # - we then call gitolite.pm's "setup_authkeys" function to do its thing # 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. # # 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. use strict; use warnings; use FindBin; BEGIN { $ENV{GL_BINDIR} = $FindBin::Bin; } 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. You MUST make sure you run the one that is in the same directory as that used by gl-auth-command (and for what that is, see ~/.ssh/authorized_keys). This should work: gl-setup-authkeys -batch keydir where 'keydir' contains a bunch of '*.pub' files.\n\n"; exit 1; } # quick sanity check and run my $keydir = shift or die "I need a directory name\n"; -d $keydir or die "$keydir should be a directory\n"; setup_authkeys($keydir); sitaramc-gitolite-6857652/src/gl-system-install000077500000000000000000000076751171610012500214610ustar00rootroot00000000000000#!/bin/sh # install the gitolite software *system wide*. Not too robust, fancy, etc., # but does have a usage message and catches simple problems. usage() { echo " Usage: $0 [shared-bin-dir shared-conf-dir shared-hooks-dir] Installs the gitolite software (just the software, not keys or repos) within \$HOME when invoked by a user, or system-wide when invoked by root. Takes 0 or 3 absolute paths. The first one must be part of \$PATH. Examples: # as root $0 # this defaults to: $0 /usr/local/bin /var/gitolite/conf /var/gitolite/hooks # as a normal user $0 # this defaults to: $0 \$HOME/bin \$HOME/share/gitolite/conf \$HOME/share/gitolite/hooks [RPM packagers: you can supply a 4th argument to specify a 'buildroot' directory. DEB folks would call this a 'DESTDIR', I believe. In this usage the first 3 arguments are NOT optional] " exit 1; } die() { echo >&2; echo "$@" >&2; echo >&2; echo Run "$0 -h" for a detailed usage message. >&2; exit 1; } [ "$1" = "-h" ] && usage validate_dir() { echo $1 | grep '^/' >/dev/null || die "$1 should be an absolute path" [ -d $1 ] || mkdir -p $1 || die "$1 does not exist and could not be created" } # if we have a buildroot, set that up first buildroot=$4; [ -n "$buildroot" ] && validate_dir $buildroot [ -n "$buildroot" ] && buildroot=$buildroot/ # either all 3 args must be supplied or none at all [ -n "$1" ] && [ -z "$3" ] && die "I need all 3 directories or none at all" # supply default values to args 1, 2, and 3 if not provided [ -z "$1" ] && { euid=`perl -e 'print $>'` if [ "$euid" = "0" ] then set /usr/local/bin /var/gitolite/conf /var/gitolite/hooks else set $HOME/bin $HOME/share/gitolite/conf $HOME/share/gitolite/hooks fi echo "using default values for EUID=$euid:" >&2 echo "$@" >&2 } gl_bin_dir=$1; validate_dir $buildroot$gl_bin_dir gl_conf_dir=$2; validate_dir $buildroot$gl_conf_dir gl_hooks_dir=$3; validate_dir $buildroot$gl_hooks_dir bindir=`echo $0 | perl -lpe 's/^/$ENV{PWD}\// unless /^\//; s/\/[^\/]+$//;'` cd $bindir/.. # we assume the standard gitolite source tree is here! cp src/* $buildroot$gl_bin_dir || die "cp src/* to $buildroot$gl_bin_dir failed" perl -lpi -e "s(^GL_PACKAGE_CONF=.*)(GL_PACKAGE_CONF=$gl_conf_dir)" $buildroot$gl_bin_dir/gl-setup # record which version is being sent across; we assume it's HEAD if git rev-parse --is-inside-work-tree >/dev/null 2>&1 then git describe --tags --long --dirty=-dt 2>/dev/null > conf/VERSION || die "git describe failed -- your git is probably too old" else [ -f conf/VERSION ] || echo '(unknown)' > conf/VERSION fi cp -R conf/* $buildroot$gl_conf_dir || die "cp conf/* to $buildroot$gl_conf_dir failed" perl -lpi \ -e "s(^#\s*\\\$GL_PACKAGE_CONF\s*=.*)(\\\$GL_PACKAGE_CONF = '$gl_conf_dir';)" \ $buildroot$gl_conf_dir/example.gitolite.rc perl -lpi \ -e "s(^#\s*\\\$GL_PACKAGE_HOOKS\s*=.*)(\\\$GL_PACKAGE_HOOKS = '$gl_hooks_dir';)" \ $buildroot$gl_conf_dir/example.gitolite.rc cp -R hooks/* $buildroot$gl_hooks_dir || die "cp hooks/* to $buildroot$gl_hooks_dir failed" # ---- # check if $gl_bin_dir is in $PATH and advise the user if needed which=`which gl-setup 2>/dev/null` path_advice=" Since gl-setup MUST be run from the PATH (and not as src/gl-setup or such), you must fix this before running gl-setup. The simplest way is to add PATH=$gl_bin_dir:\$PATH to the end of your bashrc or similar file. You can even simply run that command manually each time you log in and want to run a gitolite command." [ -z "$which" ] && die " ***** WARNING ***** gl-setup is not in your \$PATH. $path_advice" which=`dirname $which` [ "$which" = "$gl_bin_dir" ] || die " ***** WARNING ***** $which precedes $gl_bin_dir in your \$PATH, and it *also* contains gl-setup. This is almost certainly going to confuse you or me later. $path_advice" exit 0 sitaramc-gitolite-6857652/src/gl-time000077500000000000000000000027431171610012500174160ustar00rootroot00000000000000#!/usr/bin/perl # this program is a performance measurement wrapper around anything that it is # called with; it's arg-1 becomes the program being measured, with arg-2 # onwards being arg-1's arguments # sorta like the "time" command... hence the name :-) 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); # ---------------------------------------------------------------------------- # 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. # --------------------------------------------------------------- my $starttime = [gettimeofday]; my $pgm = shift; my $returncode = system($pgm, @ARGV); $returncode >>= 8; $ENV{GL_USER} = shift; my $elapsedtime = tv_interval($starttime); $ENV{GL_LOG} = get_logfilename($GL_PERFLOGT); # log_it logs to $ENV{GL_LOG} log_it("", "$elapsedtime\trc=$returncode"); sitaramc-gitolite-6857652/src/gl-tool000077500000000000000000000057131171610012500174350ustar00rootroot00000000000000#!/usr/bin/perl -w # help/instructions are at the bottom, in the __DATA__ section use strict; use warnings; use FindBin; BEGIN { $ENV{GL_BINDIR} = $FindBin::Bin; } use lib $ENV{GL_BINDIR}; use gitolite_rc; use gitolite; sub usage { print ; exit 1; } usage() unless (@ARGV); my $cmd = shift; my $pub = shift; if ($cmd eq 'add-shell-user' or $cmd eq 'add-mirroring-peer') { # sanity checks $pub or usage(); my $user = validate_pubkeyfile($pub); # write the file out, with the new authkeys line added just *before* the # gitolite section. But first, set the command that gets invoked $cmd = ( $cmd eq 'add-shell-user' ? 'gl-auth-command -s' : 'gl-mirror-shell' ); ak_insert($cmd, $user, $pub); exit 0; } die "could not understand command $cmd\n"; sub validate_pubkeyfile { my $pub = shift; -f $pub or die "$pub does not exist\n"; die "$pub contains more than one line\n" if wc_l($pub) > 1; my $user = $pub; $user =~ s(^.*/)(); # remove optional directory die "file name must end in .pub\n" unless $user =~ /(.*)\.pub$/; $user = $1; return $user; } sub ak_insert { my ($cmd, $user, $pub) = @_; # must be kept consistent with what's in src/gl-compile-conf; on the plus # side, it's not likely to change anytime soon! my $AUTH_OPTIONS = "no-port-forwarding,no-X11-forwarding,no-agent-forwarding"; my $authline = "command=\"$ENV{GL_BINDIR}/$cmd $user\",$AUTH_OPTIONS " . slurp($pub); my $authkeys = "$ENV{HOME}/.ssh/authorized_keys"; my $ak_lines = slurp($authkeys); $ak_lines =~ s/^.*$cmd $user.*\n//m; # remove existing keyline, if present $ak_lines =~ s/^# gitolite start/$authline# gitolite start/m; my $akfh = wrap_open(">", $authkeys); print $akfh $ak_lines; close $akfh; } sub wc_l { my $fh = wrap_open("<", shift); my @l = <$fh>; my $l = @l; return $l; } __DATA__ gl-tool -- make some server side tasks easier Usage: gl-tool [sub-command [args]] Security notes: this program does not do any sanitisation of input. You're running it at the CLI on the server, so you already have the power to do whatever you want anyway. current sub-commands: (1) REPLACE THE OLD $SHELL_USERS MECHANISM gl-tool add-shell-user 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. Although there is no "remove-shell-user" sub-command, you can do that quite easily by editing ~/.ssh/authorized_keys and deleting the appropriate line. (2) ADD A MIRRORING PEER KEY gl-tool add-mirroring-peer git@server.company.com.pub As above, but the given key will invoke 'gl-mirror-shell' instead of the usual 'gl-auth-command'. This is meant to be a server-to-server key, allowing (in this example), the gitolite server called 'git@server.company.com' to access this server for mirroring operations. sitaramc-gitolite-6857652/src/sshkeys-lint000077500000000000000000000112121171610012500205040ustar00rootroot00000000000000#!/usr/bin/perl use strict; use warnings; # complete rewrite of the sshkeys-lint program. Usage has changed, see # usage() function or run without arguments. use Getopt::Long; my $admin = 0; my $quiet = 0; GetOptions('admin|a=s' => \$admin, 'quiet|q' => \$quiet); use Data::Dumper; $Data::Dumper::Deepcopy = 1; $|++; my $in_gl_section = 0; my $warnings = 0; sub dbg { use Data::Dumper; for my $i (@_) { print STDERR "DBG: " . Dumper($i); } } sub msg { my $warning = shift; return if $quiet and not $warning; $warnings++ if $warning; print "sshkeys-lint: " . ( $warning ? "WARNING: " : "" ) . $_ for @_; } @ARGV or not -t or usage(); our @pubkeyfiles = @ARGV; @ARGV = (); # ------------------------------------------------------------------------ my @authkeys; my %seen_fprints; my %pkf_by_fp; msg 0, "==== checking authkeys file:\n"; fill_authkeys(); # uses up STDIN if ($admin) { my $fp = fprint("$admin.pub"); my $fpu = ( $seen_fprints{$fp}{user} || 'no access' ); # dbg("fpu = $fpu, admin=$admin"); die "\t\t*** FATAL ***\n" . "$admin.pub maps to $fpu, not $admin.\n" . "You will not be able to access gitolite with this key.\n" . "Look for the 'ssh troubleshooting' link in http://sitaramc.github.com/gitolite/.\n" if $fpu ne "user $admin"; } msg 0, "==== checking pubkeys:\n" if @pubkeyfiles; for my $pkf (@pubkeyfiles) { my $fp = fprint($pkf); msg 1, "$pkf appears to be a COPY of $pkf_by_fp{$fp}\n" if $pkf_by_fp{$fp}; $pkf_by_fp{$fp} ||= $pkf; my $fpu = ( $seen_fprints{$fp}{user} || 'no access' ); msg 0, "$pkf maps to $fpu\n"; } if ($warnings) { print "\n$warnings warnings found\n"; } exit $warnings; # ------------------------------------------------------------------------ sub fill_authkeys { while (<>) { my $seq = $.; next if ak_comment($_); # also sets/clears $in_gl_section global my $fp = fprint($_); my $user = user($_); check($seq, $fp, $user); $authkeys[$seq]{fprint} = $fp; $authkeys[$seq]{ustatus} = $user; } } sub check { my ($seq, $fp, $user) = @_; msg 1, "line $seq, $user key found *outside* gitolite section!\n" if $user =~ /^user / and not $in_gl_section; msg 1, "line $seq, $user key found *inside* gitolite section!\n" if $user !~ /^user / and $in_gl_section; if ($seen_fprints{$fp}) { msg 1, "authkeys line $seq ($user) will be ignored by sshd; " . "same key found on line " . $seen_fprints{$fp}{seq} . " (" . $seen_fprints{$fp}{user} . ")\n"; return; } $seen_fprints{$fp}{seq} = $seq; $seen_fprints{$fp}{user} = $user; } sub user { my $user = ''; $user ||= "user $1" if /^command=.*gl-auth-command (.*?)"/; $user ||= "host $1" if /^command=.*gl-mirror-shell (.*?)"/; $user ||= "unknown command" if /^command/; $user ||= "shell access" if /^ssh-(rsa|dss)/; return $user; } sub ak_comment { local $_ = shift; $in_gl_section = 1 if /^# gitolite start/; $in_gl_section = 0 if /^# gitolite end/; die "gitosis? what's that?\n" if /^#.*gitosis/; return /^\s*(#|$)/; } sub fprint { local $_ = shift; my ($fh, $tempfn, $in); if (/ssh-(dss|rsa) /) { # an actual key was passed. Since ssh-keygen requires an actual file, # make a temp file to take the data and pass on to ssh-keygen s/^.* (ssh-dss|ssh-rsa)/$1/; use File::Temp qw(tempfile); ($fh, $tempfn) = tempfile(); $in = $tempfn; print $fh $_; close $fh; } else { # a filename was passed $in = $_; } # dbg("in = $in"); -f $in or die "file not found: $in\n"; open($fh, "ssh-keygen -l -f $in |") or die "could not fork: $!\n"; my $fp = <$fh>; # dbg("fp = $fp"); close $fh; unlink $tempfn if $tempfn; die "fprint failed\n" unless $fp =~ /([0-9a-f][0-9a-f](:[0-9a-f][0-9a-f])+)/; return $1; } # ------------------------------------------------------------------------ sub usage { print < .ssh/config chmod go-w .ssh/config tar cf .ssh.save .ssh # blank out the client side rm -rf gitolite-admin .ssh tar xf .ssh.save # blank out the server side rm -rf .gitolite .gitolite.rc repositories /tmp/glrb bin share # set up our identity (impacts some tests; don't change this!) git config --global user.name "gitolite tester" git config --global user.email "tester@example.com" # install it cd gitolite git describe --tags --long HEAD > conf/VERSION # in case it is dirty src/gl-system-install cd $HOME gl-setup -q .ssh/tester.pub # add 6 keys rm -rf ~/gitolite-admin git clone gitolite:gitolite-admin cd gitolite-admin cp ~/.ssh/u*.pub keydir git add keydir; git commit -m 'added 6 keys' git push # make the rollback.tar files on both sides cd $HOME tar cf client-rollback.tar gitolite-admin mkdir -p repositories /tmp/glrb tar cf server-rollback.tar .ssh .gitolite .gitolite.rc repositories bin share tar -C /tmp -cf $HOME/server-rollback2.tar glrb sitaramc-gitolite-6857652/t/keys/000077500000000000000000000000001171610012500165535ustar00rootroot00000000000000sitaramc-gitolite-6857652/t/keys/config000066400000000000000000000005171171610012500177460ustar00rootroot00000000000000host * stricthostkeychecking no host gitolite identityfile ~/.ssh/tester host u? gitolite user gitolite-test hostname localhost host u1 identityfile ~/.ssh/u1 host u2 identityfile ~/.ssh/u2 host u3 identityfile ~/.ssh/u3 host u4 identityfile ~/.ssh/u4 host u5 identityfile ~/.ssh/u5 host u6 identityfile ~/.ssh/u6 sitaramc-gitolite-6857652/t/out/000077500000000000000000000000001171610012500164075ustar00rootroot00000000000000sitaramc-gitolite-6857652/t/out/t01-repo-groups.1000066400000000000000000000022631171610012500213600ustar00rootroot00000000000000$data_version = '2.0'; %repos = ( 'aa' => { 'R' => { 'u1' => 1, 'u2' => 1, 'u3' => 1 }, 'W' => { 'u1' => 1, 'u2' => 1, 'u3' => 1 }, 'u1' => [ [ 2, 'refs/.*', 'RW+' ] ], 'u2' => [ [ 4, 'refs/.*', 'RW' ] ], 'u3' => [ [ 5, 'refs/.*', 'RW' ] ] }, 'bb' => { 'R' => { 'u1' => 1, 'u2' => 1, 'u3' => 1 }, 'W' => { 'u1' => 1, 'u2' => 1, 'u3' => 1 }, 'u1' => [ [ 3, 'refs/.*', 'RW+' ] ], 'u2' => [ [ 6, 'refs/.*', 'RW' ] ], 'u3' => [ [ 7, 'refs/.*', 'RW' ] ] }, 'gitolite-admin' => { 'R' => { 'tester' => 1 }, 'W' => { 'tester' => 1 }, 'tester' => [ [ 0, 'refs/.*', 'RW+' ] ] }, 'testing' => { '@all' => [ [ 1, 'refs/.*', 'RW+' ] ], 'R' => { '@all' => 1 }, 'W' => { '@all' => 1 } } ); sitaramc-gitolite-6857652/t/out/t01-repo-groups.1b000066400000000000000000000023761171610012500215270ustar00rootroot00000000000000$data_version = '2.0'; %repos = ( 'aa' => { 'R' => { 'u1' => 1, 'u2' => 1, 'u3' => 1 }, 'W' => { 'u1' => 1, 'u2' => 1, 'u3' => 1 }, 'u1' => [ [ 2, 'refs/.*', 'RW+' ] ], 'u2' => [ [ 4, 'refs/.*', 'RW' ] ], 'u3' => [ [ 5, 'refs/.*', 'RW' ] ] }, 'bb' => { 'R' => { 'u1' => 1, 'u2' => 1, 'u3' => 1 }, 'W' => { 'u1' => 1, 'u2' => 1, 'u3' => 1 }, 'u1' => [ [ 3, 'refs/.*', 'RW+' ] ], 'u2' => [ [ 6, 'refs/.*', 'RW' ] ], 'u3' => [ [ 7, 'refs/.*', 'RW' ] ] }, 'gitolite-admin' => { 'R' => { 'tester' => 1 }, 'W' => { 'tester' => 1 }, 'tester' => [ [ 0, 'refs/.*', 'RW+' ] ] }, 'testing' => { '@all' => [ [ 1, 'refs/.*', 'RW+' ] ], 'R' => { '@all' => 1 }, 'W' => { '@all' => 1 } } ); %groups = ( '@g1' => { 'aa' => 'master', 'bb' => 'master' } ); sitaramc-gitolite-6857652/t/out/t01-repo-groups.1bs000066400000000000000000000026371171610012500217120ustar00rootroot00000000000000$data_version = '2.0'; %repos = (); %split_conf = ( 'aa' => 1, 'bb' => 1, 'gitolite-admin' => 1, 'testing' => 1 ); ./aa.git/gl-conf ./bb.git/gl-conf ./gitolite-admin.git/gl-conf ./testing.git/gl-conf %one_repo = ( 'aa' => { 'R' => { 'u1' => 1, 'u2' => 1, 'u3' => 1 }, 'W' => { 'u1' => 1, 'u2' => 1, 'u3' => 1 }, 'u1' => [ [ 2, 'refs/.*', 'RW+' ] ], 'u2' => [ [ 4, 'refs/.*', 'RW' ] ], 'u3' => [ [ 5, 'refs/.*', 'RW' ] ] } ); %one_repo = ( 'bb' => { 'R' => { 'u1' => 1, 'u2' => 1, 'u3' => 1 }, 'W' => { 'u1' => 1, 'u2' => 1, 'u3' => 1 }, 'u1' => [ [ 3, 'refs/.*', 'RW+' ] ], 'u2' => [ [ 6, 'refs/.*', 'RW' ] ], 'u3' => [ [ 7, 'refs/.*', 'RW' ] ] } ); %one_repo = ( 'gitolite-admin' => { 'R' => { 'tester' => 1 }, 'W' => { 'tester' => 1 }, 'tester' => [ [ 0, 'refs/.*', 'RW+' ] ] } ); %one_repo = ( 'testing' => { '@all' => [ [ 1, 'refs/.*', 'RW+' ] ], 'R' => { '@all' => 1 }, 'W' => { '@all' => 1 } } ); sitaramc-gitolite-6857652/t/out/t01-repo-groups.2000066400000000000000000000016261171610012500213630ustar00rootroot00000000000000$data_version = '2.0'; %repos = ( '@g1' => { '@g1' => [ [ 2, 'refs/.*', 'RW+' ] ], '@g2' => [ [ 3, 'refs/.*', 'RW' ] ], 'R' => { '@g1' => 1, '@g2' => 1 }, 'W' => { '@g1' => 1, '@g2' => 1 } } ); %groups = ( '@g1' => { 'aa' => 'master', 'bb' => 'master' } ); %split_conf = ( 'gitolite-admin' => 1, 'testing' => 1 ); ./gitolite-admin.git/gl-conf ./testing.git/gl-conf %one_repo = ( 'gitolite-admin' => { 'R' => { 'tester' => 1 }, 'W' => { 'tester' => 1 }, 'tester' => [ [ 0, 'refs/.*', 'RW+' ] ] } ); %one_repo = ( 'testing' => { '@all' => [ [ 1, 'refs/.*', 'RW+' ] ], 'R' => { '@all' => 1 }, 'W' => { '@all' => 1 } } ); sitaramc-gitolite-6857652/t/out/t02-user-groups.1000066400000000000000000000014461171610012500213740ustar00rootroot00000000000000$data_version = '2.0'; %repos = ( 'aa' => { 'R' => { 'u1' => 1, 'u2' => 1, 'u3' => 1 }, 'W' => { 'u1' => 1, 'u2' => 1, 'u3' => 1 }, 'u1' => [ [ 2, 'refs/.*', 'RW+' ] ], 'u2' => [ [ 3, 'refs/.*', 'RW' ] ], 'u3' => [ [ 4, 'refs/.*', 'RW' ] ] }, 'gitolite-admin' => { 'R' => { 'tester' => 1 }, 'W' => { 'tester' => 1 }, 'tester' => [ [ 0, 'refs/.*', 'RW+' ] ] }, 'testing' => { '@all' => [ [ 1, 'refs/.*', 'RW+' ] ], 'R' => { '@all' => 1 }, 'W' => { '@all' => 1 } } ); sitaramc-gitolite-6857652/t/out/t02-user-groups.1b000066400000000000000000000017531171610012500215370ustar00rootroot00000000000000$data_version = '2.0'; %repos = ( 'aa' => { 'R' => { 'u1' => 1, 'u2' => 1, 'u3' => 1 }, 'W' => { 'u1' => 1, 'u2' => 1, 'u3' => 1 }, 'u1' => [ [ 2, 'refs/.*', 'RW+' ] ], 'u2' => [ [ 3, 'refs/.*', 'RW' ] ], 'u3' => [ [ 4, 'refs/.*', 'RW' ] ] }, 'gitolite-admin' => { 'R' => { 'tester' => 1 }, 'W' => { 'tester' => 1 }, 'tester' => [ [ 0, 'refs/.*', 'RW+' ] ] }, 'testing' => { '@all' => [ [ 1, 'refs/.*', 'RW+' ] ], 'R' => { '@all' => 1 }, 'W' => { '@all' => 1 } } ); %groups = ( '@g1' => { 'u1' => 'master' }, '@g2' => { 'u2' => 'master', 'u3' => 'master' }, '@g3' => { 'u4' => 'master', 'u5' => 'master', 'u6' => 'master' } ); sitaramc-gitolite-6857652/t/out/t02-user-groups.1bs000066400000000000000000000017441171610012500217220ustar00rootroot00000000000000$data_version = '2.0'; %repos = (); %split_conf = ( 'aa' => 1, 'gitolite-admin' => 1, 'testing' => 1 ); ./aa.git/gl-conf ./gitolite-admin.git/gl-conf ./testing.git/gl-conf %one_repo = ( 'aa' => { 'R' => { 'u1' => 1, 'u2' => 1, 'u3' => 1 }, 'W' => { 'u1' => 1, 'u2' => 1, 'u3' => 1 }, 'u1' => [ [ 2, 'refs/.*', 'RW+' ] ], 'u2' => [ [ 3, 'refs/.*', 'RW' ] ], 'u3' => [ [ 4, 'refs/.*', 'RW' ] ] } ); %one_repo = ( 'gitolite-admin' => { 'R' => { 'tester' => 1 }, 'W' => { 'tester' => 1 }, 'tester' => [ [ 0, 'refs/.*', 'RW+' ] ] } ); %one_repo = ( 'testing' => { '@all' => [ [ 1, 'refs/.*', 'RW+' ] ], 'R' => { '@all' => 1 }, 'W' => { '@all' => 1 } } ); sitaramc-gitolite-6857652/t/out/t02-user-groups.2000066400000000000000000000015771171610012500214020ustar00rootroot00000000000000$data_version = '2.0'; %repos = ( 'aa' => { '@g1' => [ [ 2, 'refs/.*', 'RW+' ] ], '@g2' => [ [ 3, 'refs/.*', 'RW' ] ], 'R' => { '@g1' => 1, '@g2' => 1 }, 'W' => { '@g1' => 1, '@g2' => 1 } }, 'gitolite-admin' => { 'R' => { 'tester' => 1 }, 'W' => { 'tester' => 1 }, 'tester' => [ [ 0, 'refs/.*', 'RW+' ] ] }, 'testing' => { '@all' => [ [ 1, 'refs/.*', 'RW+' ] ], 'R' => { '@all' => 1 }, 'W' => { '@all' => 1 } } ); %groups = ( '@g1' => { 'u1' => 'master' }, '@g2' => { 'u2' => 'master', 'u3' => 'master' }, '@g3' => { 'u4' => 'master', 'u5' => 'master', 'u6' => 'master' } ); sitaramc-gitolite-6857652/t/out/t02-user-groups.2bs000066400000000000000000000020751171610012500217210ustar00rootroot00000000000000$data_version = '2.0'; %repos = (); %groups = ( '@g1' => { 'u1' => 'master' }, '@g2' => { 'u2' => 'master', 'u3' => 'master' }, '@g3' => { 'u4' => 'master', 'u5' => 'master', 'u6' => 'master' } ); %split_conf = ( 'aa' => 1, 'gitolite-admin' => 1, 'testing' => 1 ); ./aa.git/gl-conf ./gitolite-admin.git/gl-conf ./testing.git/gl-conf %one_repo = ( 'aa' => { '@g1' => [ [ 2, 'refs/.*', 'RW+' ] ], '@g2' => [ [ 3, 'refs/.*', 'RW' ] ], 'R' => { '@g1' => 1, '@g2' => 1 }, 'W' => { '@g1' => 1, '@g2' => 1 } } ); %one_repo = ( 'gitolite-admin' => { 'R' => { 'tester' => 1 }, 'W' => { 'tester' => 1 }, 'tester' => [ [ 0, 'refs/.*', 'RW+' ] ] } ); %one_repo = ( 'testing' => { '@all' => [ [ 1, 'refs/.*', 'RW+' ] ], 'R' => { '@all' => 1 }, 'W' => { '@all' => 1 } } ); sitaramc-gitolite-6857652/t/out/t04-wild1.1000066400000000000000000000000461171610012500201160ustar00rootroot00000000000000New perms are: READERS u5 WRITERS u6 sitaramc-gitolite-6857652/t/out/t04-wild1.2000066400000000000000000000000271171610012500201160ustar00rootroot00000000000000READERS u5 WRITERS u6 sitaramc-gitolite-6857652/t/rollback000077500000000000000000000001551171610012500173200ustar00rootroot00000000000000#!/bin/bash cd $HOME rm -rf gitolite-admin td tar xf client-rollback.tar mkdir td $TESTDIR/rollback.server sitaramc-gitolite-6857652/t/rollback.server000077500000000000000000000002611171610012500206230ustar00rootroot00000000000000#!/bin/bash cd $HOME rm -rf .ssh .gitolite .gitolite.rc repositories gitolite-install /tmp/glrb bin share tar xf server-rollback.tar ( cd /tmp; tar xf ~/server-rollback2.tar ) sitaramc-gitolite-6857652/t/t-fedora-big-config000066400000000000000000000006041171610012500212260ustar00rootroot00000000000000# vim: syn=sh: cd $TESTDIR $TESTDIR/rollback || die "rollback failed" editrc GL_BIG_CONFIG 1 editrc GL_NO_DAEMON_NO_GITWEB 1 editrc GL_NO_CREATE_REPOS 1 editrc GL_NO_SETUP_AUTHKEYS 1 catrc expect "GL_BIG_CONFIG *= *1;" # ---------- name "first round" date | hl cat data/fedora.conf | ugc date | hl time sync name "second round" date | hl echo " repo foo RW+ = u1 u2 " | ugc date | hl sitaramc-gitolite-6857652/t/t00-initial000066400000000000000000000027471171610012500175670ustar00rootroot00000000000000# vim: syn=sh: $TESTDIR/rollback || die "rollback failed" # ---------- name "basic push admin repo" echo " repo aa RW+ = u1 RW = u2 u3 " | ugc expect "To gitolite:gitolite-admin" expect "master -> master" name "basic create repo" expect "remote: Initialized empty Git repository in $TEST_BASE_FULL/aa.git/" # ---------- name "basic clone" cd ~/td runlocal git clone u1:aa u1aa expect "Cloning into 'u1aa'..." expect "warning: You appear to have cloned an empty repository" runlocal ls -ald --time-style=long-iso u1aa expect "drwxr-xr-x 3 $USER $USER 4096 201.-..-.. ..:.. u1aa" # ---------- name "basic clone deny" cd ~/td runlocal git clone u4:aa u4aa expect "R access for aa DENIED to u4" runlocal ls -ald u4aa expect "ls: cannot access u4aa: No such file or directory" # ---------- name "basic push" cd ~/td/u1aa mdc runlocal git push origin HEAD expect "To u1:aa" expect "\[new branch\] *HEAD -> master" # ---------- name "basic rewind" cd ~/td/u1aa mdc mdc mdc runlocal git push origin HEAD runlocal git reset --hard HEAD^ mdc runlocal git push -f origin HEAD expect "+ .* HEAD -> master (forced update)" name "basic rewind log" taillog expect " u1 .* + .* aa refs/heads/master refs/.\*" # ---------- name "basic rewind deny" cd ~/td runlocal git clone u2:aa u2aa cd ~/td/u2aa mdc mdc mdc runlocal git push origin HEAD runlocal git reset --hard HEAD^ mdc runlocal git push -f origin HEAD expect "remote: + refs/heads/master aa u2 DENIED by fallthru" name INTERNAL sitaramc-gitolite-6857652/t/t01-repo-groups000066400000000000000000000021031171610012500204030ustar00rootroot00000000000000# vim: syn=sh: # ---------- $TESTDIR/rollback || die "rollback failed" editrc GL_BIG_CONFIG 0 name "base output with no groups" echo " repo aa bb RW+ = u1 RW = u2 u3 " | ugc catconf expect_filesame $TESTDIR/out/t01-repo-groups.1 # ---------- $TESTDIR/rollback || die "rollback failed" editrc GL_BIG_CONFIG 0 name "output with eqvt repo groups" echo " @g1 = aa bb repo @g1 RW+ = u1 RW = u2 u3 " | ugc catconf expect_filesame $TESTDIR/out/t01-repo-groups.1b # ---------- $TESTDIR/rollback || die "rollback failed" editrc GL_BIG_CONFIG 1 name "base output with no groups but GL_BIG_CONFIG on" echo " repo aa bb RW+ = u1 RW = u2 u3 " | ugc catconfs expect_filesame $TESTDIR/out/t01-repo-groups.1bs # ---------- $TESTDIR/rollback || die "rollback failed" editrc GL_BIG_CONFIG 1 name "repo groups with GL_BIG_CONFIG on" echo " @g1 = aa bb repo @g1 RW+ = @g1 RW = @g2 " | ugc catconfs expect_filesame $TESTDIR/out/t01-repo-groups.2 name INTERNAL sitaramc-gitolite-6857652/t/t02-user-groups000066400000000000000000000021761171610012500204270ustar00rootroot00000000000000# vim: syn=sh: # ---------- $TESTDIR/rollback || die "rollback failed" editrc GL_BIG_CONFIG 0 name "base output with no groups" echo " repo aa RW+ = u1 RW = u2 u3 " | ugc catconf expect_filesame $TESTDIR/out/t02-user-groups.1 # ---------- $TESTDIR/rollback || die "rollback failed" editrc GL_BIG_CONFIG 0 name "output with eqvt user groups" echo " @g1 = u1 @g2 = u2 u3 @g3 = u4 u5 u6 repo aa RW+ = @g1 RW = @g2 " | ugc catconf expect_filesame $TESTDIR/out/t02-user-groups.1b # ---------- $TESTDIR/rollback || die "rollback failed" editrc GL_BIG_CONFIG 1 name "base output with no groups but GL_BIG_CONFIG on" echo " repo aa RW+ = u1 RW = u2 u3 " | ugc catconfs expect_filesame $TESTDIR/out/t02-user-groups.1bs # ---------- $TESTDIR/rollback || die "rollback failed" editrc GL_BIG_CONFIG 1 name "user groups with GL_BIG_CONFIG on" echo " @g1 = u1 @g2 = u2 u3 @g3 = u4 u5 u6 repo aa RW+ = @g1 RW = @g2 " | ugc catconfs expect_filesame $TESTDIR/out/t02-user-groups.2bs name INTERNAL sitaramc-gitolite-6857652/t/t03-branch-permissions000066400000000000000000000006231171610012500217360ustar00rootroot00000000000000# vim: syn=sh: # ---------- $TESTDIR/rollback || die "rollback failed" editrc GL_BIG_CONFIG 0 echo ===== testing with GL_BIG_CONFIG set to 0 ===== cd $TESTDIR . ./t03a-branch-permissions cd $TESTDIR # ---------- $TESTDIR/rollback || die "rollback failed" editrc GL_BIG_CONFIG 1 echo ===== testing with GL_BIG_CONFIG set to 1 ===== cd $TESTDIR . ./t03a-branch-permissions cd $TESTDIR name INTERNAL sitaramc-gitolite-6857652/t/t03a-branch-permissions000066400000000000000000000105021171610012500220740ustar00rootroot00000000000000# vim: syn=sh: # ---------- name "setup" echo " @g1 = u1 @g2 = u2 @g3 = u3 @gaa = aa repo @gaa RW+ = @g1 RW = @g2 RW+ master = @g3 RW master = u4 - master = u5 RW+ dev = u5 RW = u5 " | ugc # reasonably complex setup; we'll do everything from one repo though cd ~/td runlocal git clone u1:aa cd ~/td/aa mdc; mdc; mdc mdc; mdc; mdc mdc; mdc; mdc name "INTERNAL" runlocal git push origin HEAD expect "To u1:aa" expect "\* \[new branch\] HEAD -> master" git branch dev git branch foo # u1 tries to rewind master; succeeds name "u1 rewind master succeed" runlocal git reset --hard HEAD^ mdc runlocal git push origin +master expect "+ .* master -> master (forced update)" # u2 tries to rewind master; fails name "u2 rewind master fail" runlocal git reset --hard HEAD^ mdc runlocal git push u2:aa +master expect "remote: + refs/heads/master aa u2 DENIED by fallthru" # u3 tries to rewind master; succeeds name "u3 rewind master succeed" runlocal git reset --hard HEAD^ mdc runlocal git push u3:aa +master expect "+ .* master -> master (forced update)" # u4 tries to push master; succeeds name "u4 push master succeed" mdc runlocal git push u4:aa master expect "master -> master" # u4 then tries to rewind master; fails name "u4 rewind master fail" runlocal git reset --hard HEAD^ runlocal git push u4:aa +master expect "remote: + refs/heads/master aa u4 DENIED by fallthru" # u3 and u4 try to push the other 2 branches; fail name "u3 and u4 / dev foo -- all 4 fail" runlocal git push u3:aa dev expect "remote: W refs/heads/dev aa u3 DENIED by fallthru" runlocal git push u4:aa dev expect "remote: W refs/heads/dev aa u4 DENIED by fallthru" runlocal git push u3:aa foo expect "remote: W refs/heads/foo aa u3 DENIED by fallthru" runlocal git push u4:aa foo expect "remote: W refs/heads/foo aa u4 DENIED by fallthru" # clean up for next set runlocal git push -f origin master dev foo # u5 tries to push master; fails name "u5 push master fail" mdc runlocal git push u5:aa master expect "remote: W refs/heads/master aa u5 DENIED by refs/heads/master" # u5 tries to rewind dev; succeeds name "u5 rewind dev succeed" runlocal git push u5:aa +dev^:dev expect "+ .* dev^ -> dev (forced update)" # u5 tries to rewind foo; fails name "u5 rewind foo fail" runlocal git push u5:aa +foo^:foo expect "remote: + refs/heads/foo aa u5 DENIED by fallthru" # u5 tries to push foo; succeeds name "INTERNAL" runlocal git checkout foo expect "Switched to branch 'foo'" name "u5 push foo succeed" mdc runlocal git push u5:aa foo expect "foo -> foo" # u1 tries to delete branch dev name "u1 delete branch dev succeed" runlocal git push origin :dev expect " - \[deleted\] *dev" # quietly push it back again name "INTERNAL" runlocal git push origin dev expect " * \[new branch\] dev -> dev" # u1 tries to delete dev on a new setup name "INTERNAL" echo " repo @gaa RWD dev = u4 " | ugc expect "master -> master" # u1 tries to delete branch dev; fails name "u1 delete branch dev fail" runlocal git push origin :dev expect "remote: D refs/heads/dev aa u1 DENIED by fallthru" # u4 tries to delete branch dev; succeeds name "u4 delete branch dev succeed" runlocal git push u4:aa :dev expect " - \[deleted\] *dev" # ---------- name "INTERNAL" echo " @gr1 = r1 repo @gr1 RW refs/heads/v[0-9] = u1 RW refs/heads = tester " | ugc notexpect ABORT cd ~/td runlocal git clone gitolite:r1 expect "Cloning into 'r1'..." cd ~/td/r1 mdc; mdc; mdc mdc; mdc; mdc runlocal git push origin HEAD expect "\* \[new branch\] HEAD -> master" runlocal git branch v1 runlocal git push origin v1 name "no deny rules -- push v1 succeeds" expect "\* \[new branch\] v1 -> v1" name "INTERNAL" echo " @gr2 = r2 repo @gr2 RW refs/heads/v[0-9] = u1 - refs/heads/v[0-9] = tester RW refs/heads = tester " | ugc notexpect ABORT cd ~/td runlocal git clone gitolite:r2 expect "Cloning into 'r2'..." cd ~/td/r2 mdc; mdc; mdc mdc; mdc; mdc runlocal git push origin HEAD expect "\* \[new branch\] HEAD -> master" runlocal git branch v1 runlocal git push origin v1 name "deny rules -- push v1 fails" expect "remote: W refs/heads/v1 r2 tester DENIED by refs/heads/v\[0-9\]" name "INTERNAL" sitaramc-gitolite-6857652/t/t04-wild000066400000000000000000000005011171610012500170630ustar00rootroot00000000000000# vim: syn=sh: # ---------- for i in 0 1 do for j in students all do hl t04-wild with GL_WILDREPOS $i, C=@$j cd $TESTDIR $TESTDIR/rollback || die "rollback failed" editrc GL_BIG_CONFIG $i editrc GL_WILDREPOS 1 . ./t04a-wild-$j cd $TESTDIR done done sitaramc-gitolite-6857652/t/t04a-wild-all000066400000000000000000000070201171610012500177750ustar00rootroot00000000000000# vim: syn=sh: # ---------- name "INTERNAL" echo " @prof = u1 @TAs = u2 u3 @students = u4 u5 u6 @gfoo = foo/CREATOR/a[0-9][0-9] repo @gfoo C = @all RW+ = CREATOR RW = WRITERS @TAs R = READERS @prof " | ugc notexpect ABORT # reasonably complex setup; we'll do everything from one repo though cd ~/td name "u1 create success" runlocal git clone u1:foo/u1/a01 expect "Initialized empty Git repository in $TEST_BASE_FULL/foo/u1/a01.git/" # expect "R access for foo/u1/a01 DENIED to u1" name "u2 create success" runlocal git clone u2:foo/u2/a02 expect "Initialized empty Git repository in $TEST_BASE_FULL/foo/u2/a02.git/" # expect "R access for foo/u2/a02 DENIED to u2" name "u4 tries to create u2 repo" runlocal git clone u4:foo/u2/a12 expect "R access for foo/u2/a12 DENIED to u4" name "line anchored regexes" runlocal git clone u4:foo/u4/a1234 expect "R access for foo/u4/a1234 DENIED to u4" name "u4 tries to create his own repo" runlocal git clone u4:foo/u4/a12 expect "Initialized empty Git repository in $TEST_BASE_FULL/foo/u4/a12.git/" expect "warning: You appear to have cloned an empty repository." name "u4 push success" cd ~/td/a12 mdc;mdc;mdc;mdc runlocal git push origin master expect "To u4:foo/u4/a12" expect "\* \[new branch\] master -> master" name "u1 clone success" cd ~/td runlocal git clone u1:foo/u4/a12 u1a12 expect "Cloning into 'u1a12'..." name "u1 push fail" cd ~/td/u1a12 mdc; mdc runlocal git push expect "W access for foo/u4/a12 DENIED to u1" name "u2 clone success" cd ~/td runlocal git clone u2:foo/u4/a12 u2a12 expect "Cloning into 'u2a12'..." name "u2 push success" cd ~/td/u2a12 mdc; mdc runlocal git push expect "To u2:foo/u4/a12" expect "master -> master" name "u2 rewind fail" runlocal git push -f origin master^:master expect "remote: + refs/heads/master foo/u4/a12 u2 DENIED by fallthru" expect "remote: error: hook declined to update refs/heads/master" expect "To u2:foo/u4/a12" expect "\[remote rejected\] master^ -> master (hook declined)" expect "error: failed to push some refs to 'u2:foo/u4/a12'" name INTERNAL # u4 pull to sync up cd ~/td/a12 runlocal git pull expect "Fast-forward" expect "From u4:foo/u4/a12" expect "master -> origin/master" name "u4 rewind success" runlocal git reset --hard HEAD^ runlocal git push -f expect "To u4:foo/u4/a12" expect "+ .* master -> master (forced update)" name "u5 clone fail" cd ~/td runlocal git clone u5:foo/u4/a12 u5a12 expect "R access for foo/u4/a12 DENIED to u5" name "setperm" echo " R u5 RW u6 " | runlocal ssh u4 setperms foo/u4/a12 expect_filesame $TESTDIR/out/t04-wild1.1 name "getperms" runlocal ssh u4 getperms foo/u4/a12 expect_filesame $TESTDIR/out/t04-wild1.2 name "u5 clone success" cd ~/td runlocal git clone u5:foo/u4/a12 u5a12 expect "Cloning into 'u5a12'..." name "u5 push fail" cd ~/td/u5a12 mdc; mdc runlocal git push expect "W access for foo/u4/a12 DENIED to u5" name "u6 clone success" cd ~/td runlocal git clone u6:foo/u4/a12 u6a12 expect "Cloning into 'u6a12'..." name "u6 push success" cd ~/td/u6a12 mdc; mdc runlocal git push u6:foo/u4/a12 expect "To u6:foo/u4/a12" expect "master -> master" name "u6 rewind fail" runlocal git push -f u6:foo/u4/a12 master^:master expect "remote: + refs/heads/master foo/u4/a12 u6 DENIED by fallthru" expect "remote: error: hook declined to update refs/heads/master" expect "To u6:foo/u4/a12" expect "\[remote rejected\] master^ -> master (hook declined)" expect "error: failed to push some refs to 'u6:foo/u4/a12'" name "INTERNAL" sitaramc-gitolite-6857652/t/t04a-wild-students000066400000000000000000000065611171610012500211070ustar00rootroot00000000000000# vim: syn=sh: # ---------- name "INTERNAL" echo " @prof = u1 @TAs = u2 u3 @students = u4 u5 u6 @gfoo = foo/CREATOR/a[0-9][0-9] repo @gfoo C = @students RW+ = CREATOR RW = WRITERS @TAs R = READERS @prof " | ugc notexpect ABORT # reasonably complex setup; we'll do everything from one repo though cd ~/td name "u1 create fail" runlocal git clone u1:foo/u1/a01 expect "R access for foo/u1/a01 DENIED to u1" name "u2 create fail" runlocal git clone u2:foo/u2/a02 expect "R access for foo/u2/a02 DENIED to u2" name "u4 tries to create u2 repo" runlocal git clone u4:foo/u2/a12 expect "R access for foo/u2/a12 DENIED to u4" name "line anchored regexes" runlocal git clone u4:foo/u4/a1234 expect "R access for foo/u4/a1234 DENIED to u4" name "u4 tries to create his own repo" runlocal git clone u4:foo/u4/a12 expect "Initialized empty Git repository in $TEST_BASE_FULL/foo/u4/a12.git/" expect "warning: You appear to have cloned an empty repository." name "u4 push success" cd ~/td/a12 mdc;mdc;mdc;mdc runlocal git push origin master expect "To u4:foo/u4/a12" expect "\* \[new branch\] master -> master" name "u1 clone success" cd ~/td runlocal git clone u1:foo/u4/a12 u1a12 expect "Cloning into 'u1a12'..." name "u1 push fail" cd ~/td/u1a12 mdc; mdc runlocal git push expect "W access for foo/u4/a12 DENIED to u1" name "u2 clone success" cd ~/td runlocal git clone u2:foo/u4/a12 u2a12 expect "Cloning into 'u2a12'..." name "u2 push success" cd ~/td/u2a12 mdc; mdc runlocal git push expect "To u2:foo/u4/a12" expect "master -> master" name "u2 rewind fail" runlocal git push -f origin master^:master expect "remote: + refs/heads/master foo/u4/a12 u2 DENIED by fallthru" expect "remote: error: hook declined to update refs/heads/master" expect "To u2:foo/u4/a12" expect "\[remote rejected\] master^ -> master (hook declined)" expect "error: failed to push some refs to 'u2:foo/u4/a12'" name INTERNAL # u4 pull to sync up cd ~/td/a12 runlocal git pull expect "Fast-forward" expect "From u4:foo/u4/a12" expect "master -> origin/master" name "u4 rewind success" runlocal git reset --hard HEAD^ runlocal git push -f expect "To u4:foo/u4/a12" expect "+ .* master -> master (forced update)" name "u5 clone fail" cd ~/td runlocal git clone u5:foo/u4/a12 u5a12 expect "R access for foo/u4/a12 DENIED to u5" name "setperm" echo " R u5 RW u6 " | runlocal ssh u4 setperms foo/u4/a12 expect_filesame $TESTDIR/out/t04-wild1.1 name "getperms" runlocal ssh u4 getperms foo/u4/a12 expect_filesame $TESTDIR/out/t04-wild1.2 name "u5 clone success" cd ~/td runlocal git clone u5:foo/u4/a12 u5a12 expect "Cloning into 'u5a12'..." name "u5 push fail" cd ~/td/u5a12 mdc; mdc runlocal git push expect "W access for foo/u4/a12 DENIED to u5" name "u6 clone success" cd ~/td runlocal git clone u6:foo/u4/a12 u6a12 expect "Cloning into 'u6a12'..." name "u6 push success" cd ~/td/u6a12 mdc; mdc runlocal git push u6:foo/u4/a12 expect "To u6:foo/u4/a12" expect "master -> master" name "u6 rewind fail" runlocal git push -f u6:foo/u4/a12 master^:master expect "remote: + refs/heads/master foo/u4/a12 u6 DENIED by fallthru" expect "remote: error: hook declined to update refs/heads/master" expect "To u6:foo/u4/a12" expect "\[remote rejected\] master^ -> master (hook declined)" expect "error: failed to push some refs to 'u6:foo/u4/a12'" name "INTERNAL" sitaramc-gitolite-6857652/t/t05-delegation000066400000000000000000000004061171610012500202440ustar00rootroot00000000000000# vim: syn=sh: # ---------- for i in 0 1 do hl t05a-delegation with GL_BIG_CONFIG $i . ./t05a-delegation $i cd $TESTDIR done for i in 0 1 do hl t05b-delegation-wild with GL_BIG_CONFIG $i . ./t05b-delegation-wild $i cd $TESTDIR done sitaramc-gitolite-6857652/t/t05a-delegation000066400000000000000000000050321171610012500204050ustar00rootroot00000000000000# vim: syn=sh: cd $TESTDIR $TESTDIR/rollback || die "rollback failed" editrc GL_BIG_CONFIG $1 # ---------- name "INTERNAL" echo " # group your projects/repos however you want @u1r = r1a r1b @u2r = r2a r2b @u3r = r3a r3b # the admin repo access was probably like this to start with: repo gitolite-admin RW = u1 u2 u3 RW+ NAME/ = tester RW NAME/conf/fragments/u1r = u1 RW NAME/conf/fragments/u2r = u2 RW NAME/conf/fragments/u3r = u3 " | ugc notexpect ABORT name "tester push frag u1r" cd ~/gitolite-admin mkdir -p conf/fragments echo " repo @u1r RW+ = tester " > conf/fragments/u1r.conf ugc < /dev/null expect "create mode 100644 conf/fragments/u1r.conf" expect "remote: Initialized empty Git repository in $TEST_BASE_FULL/r1a.git/" expect "To gitolite:gitolite-admin" expect "master -> master" name "u1 push frag u1r" cd ~/gitolite-admin echo " repo @u1r RW+ = u5 " > conf/fragments/u1r.conf ugc u1 < /dev/null expect "To u1:gitolite-admin" expect "master -> master" name "u2 push main conf fail" cd ~/gitolite-admin echo " repo @u1r RW+ = u6 " | ugc u2 expect "W NAME/conf/gitolite.conf gitolite-admin u2 DENIED by fallthru" expect "To u2:gitolite-admin" expect "\[remote rejected\] master -> master (hook declined)" git reset --hard origin/master &>/dev/null name "u2 push frag u1r fail" cd ~/gitolite-admin echo " repo @u1r RW+ = u6 " > conf/fragments/u1r.conf ugc u2 < /dev/null expect "remote: W NAME/conf/fragments/u1r.conf gitolite-admin u2 DENIED by fallthru" expect "To u2:gitolite-admin" expect "\[remote rejected\] master -> master (hook declined)" git reset --hard origin/master &>/dev/null name "u3 set perms for r2a fail" cd ~/gitolite-admin echo " repo r2a RW+ = u6 " > conf/fragments/u3r.conf ugc u3 < /dev/null expect "u3r.conf attempting to set access for r2a" git reset --hard origin/master &>/dev/null name "u3 add r2b to u3r fail" cd ~/gitolite-admin echo " @u3r = r2b repo @u3r RW+ = u6 " > conf/fragments/u3r.conf ugc u3 < /dev/null [[ $1 == 0 ]] && expect "u3r.conf attempting to set access for r2b" [[ $1 == 1 ]] && expect "u3r.conf attempting to set access for locally modified @u3r" [[ $1 == 1 ]] && notexpect "u3r.conf attempting to set access for r2b" [[ $1 == 0 ]] && notexpect "defining groups is not allowed inside fragments" git reset --hard origin/master &>/dev/null name INTERNAL sitaramc-gitolite-6857652/t/t05b-delegation-wild000066400000000000000000000056311171610012500213500ustar00rootroot00000000000000# vim: syn=sh: cd $TESTDIR $TESTDIR/rollback || die "rollback failed" editrc GL_BIG_CONFIG $i editrc GL_WILDREPOS 1 # ---------- name "INTERNAL" cd ~/gitolite-admin mkdir -p conf/fragments echo " # group your projects/repos however you want @u1r = r1[ab] @u2r = r2[ab] @u3r = r3[ab] # the admin repo access was probably like this to start with: repo gitolite-admin RW = u1 u2 u3 RW+ NAME/ = tester RW NAME/conf/fragments/u1r = u1 RW NAME/conf/fragments/u2r = u2 RW NAME/conf/fragments/u3r = u3 " | ugc echo " repo r1a r1b C = @all RW+ = CREATOR repo @u1r RW+ = tester " > conf/fragments/u1r.conf echo " repo @u2r C = @all RW+ = CREATOR repo @u2r RW+ = tester " > conf/fragments/u2r.conf echo " repo @u3r C = @all RW+ = CREATOR repo @u3r RW+ = tester " > conf/fragments/u3r.conf ugc < /dev/null runlocal git ls-remote gitolite:r1a runlocal git ls-remote gitolite:r1b runlocal git ls-remote gitolite:r2a runlocal git ls-remote gitolite:r2b runlocal git ls-remote gitolite:r3a runlocal git ls-remote gitolite:r3b runremote ls repositories name "u1 push frag u1r" cd ~/gitolite-admin echo " repo @u1r RW+ = u5 " > conf/fragments/u1r.conf ugc u1 < /dev/null expect "To u1:gitolite-admin" expect "master -> master" name "u2 push main conf fail" cd ~/gitolite-admin echo " repo @u1r RW+ = u6 " | ugc u2 expect "W NAME/conf/gitolite.conf gitolite-admin u2 DENIED by fallthru" expect "To u2:gitolite-admin" expect "\[remote rejected\] master -> master (hook declined)" git reset --hard origin/master &>/dev/null name "u2 push frag u1r fail" cd ~/gitolite-admin echo " repo @u1r RW+ = u6 " > conf/fragments/u1r.conf ugc u2 < /dev/null expect "remote: W NAME/conf/fragments/u1r.conf gitolite-admin u2 DENIED by fallthru" expect "To u2:gitolite-admin" expect "\[remote rejected\] master -> master (hook declined)" git reset --hard origin/master &>/dev/null name "u3 set perms for r2a fail" cd ~/gitolite-admin echo " repo r2a RW+ = u6 " > conf/fragments/u3r.conf ugc u3 < /dev/null expect "u3r.conf attempting to set access for r2a" git reset --hard origin/master &>/dev/null name "u3 add r2b to u3r fail" cd ~/gitolite-admin echo " @u3r = r2b repo @u3r RW+ = u6 " > conf/fragments/u3r.conf ugc u3 < /dev/null [[ $1 == 0 ]] && expect "u3r.conf attempting to set access for r2b" [[ $1 == 1 ]] && expect "u3r.conf attempting to set access for locally modified @u3r" [[ $1 == 1 ]] && notexpect "u3r.conf attempting to set access for r2b" [[ $1 == 0 ]] && notexpect "defining groups is not allowed inside fragments" git reset --hard origin/master &>/dev/null name INTERNAL sitaramc-gitolite-6857652/t/t09-oldtests000066400000000000000000000002071171610012500177750ustar00rootroot00000000000000# vim: syn=sh: # ---------- for i in 0 1 do hl t09-oldtests with GL_BIG_CONFIG $i . ./t09a-oldtests $i cd $TESTDIR done sitaramc-gitolite-6857652/t/t09a-oldtests000066400000000000000000000021411171610012500201350ustar00rootroot00000000000000# vim: syn=sh: cd $TESTDIR $TESTDIR/rollback || die "rollback failed" editrc GL_BIG_CONFIG $1 name "bad repo name" echo " repo abc*def RW = tester " | ugc expect "To gitolite:gitolite-admin" expect "master -> master" [[ $1 == 0 ]] && expect "ABORTING" [[ $1 == 0 ]] && expect "bad reponame 'abc\*def' or you forgot to set .GL_WILDREPOS" name "bad user name" echo " repo abc RW = sitaram*tester repo abcdef RW = sitaram " | ugc -r expect "ABORTING" expect "bad username 'sitaram\*tester'" name "NAME deny" echo " repo abc RW = u1 - NAME/i = u1 RW NAME/j = u1 RW NAME/u = u1 " | ugc -r notexpect "failed to push" cd ~/td runlocal git clone u1:abc expect "Cloning into 'abc'..." cd ~/td/abc mdc jfile; runlocal git push origin master expect "To u1:abc" expect "\[new branch\] master -> master" mdc ufile; runlocal git push origin master expect "To u1:abc" expect "master -> master" mdc ifile; runlocal git push origin master expect "remote: W NAME/ifile abc u1 DENIED by NAME/i" name INTERNAL sitaramc-gitolite-6857652/t/t10-all-yall000066400000000000000000000050241171610012500176350ustar00rootroot00000000000000# vim: syn=sh: has_export_ok() { runremote ls -al $TEST_BASE_FULL/$1.git/git-daemon-export-ok expect "$USER $USER .* $TEST_BASE_FULL/$1.git/git-daemon-export-ok" } does_not_have_export_ok() { runremote ls -al $TEST_BASE_FULL/$1.git/git-daemon-export-ok expect "ls: cannot access $TEST_BASE_FULL/$1.git/git-daemon-export-ok: No such file or directory" } is_in_projects_list() { runremote cat projects.list expect "^$1.git$" } is_not_in_projects_list() { runremote cat projects.list notexpect "^$1.git$" } for bc in 0 1 do for ais in 0 1 do cd $TESTDIR $TESTDIR/rollback || die "rollback failed" editrc GL_WILDREPOS 1 editrc GL_BIG_CONFIG $bc echo "\$GL_ALL_INCLUDES_SPECIAL = $ais;" | addrc name "INTERNAL" echo " repo @all R = @all repo foo RW+ = u1 repo bar RW+ = u2 repo dev/..* C = u3 u4 RW+ = CREATOR " | ugc name "setup" expect_push_ok "master -> master" cd ~/td runlocal git ls-remote u1:dev/wild1 expect "fatal: '$TEST_BASE_FULL/dev/wild1.git' does not appear to be a git repository" expect "fatal: The remote end hung up unexpectedly" runlocal git clone u3:dev/wild1 expect "Cloning into 'wild1'..." expect "Initialized empty Git repository in $TEST_BASE_FULL/dev/wild1.git/" expect "warning: You appear to have cloned an empty repository." cd wild1 mdc; mdc runlocal git push origin master:wild1 expect "To u3:dev/wild1" expect "* \[new branch\] master -> wild1" runlocal git push u1:foo master:br-foo expect "To u1:foo" expect "* \[new branch\] master -> br-foo" runlocal git push u2:bar master:br-bar expect "To u2:bar" expect "* \[new branch\] master -> br-bar" runlocal git ls-remote u6:foo expect refs/heads/br-foo runlocal git ls-remote u6:bar expect refs/heads/br-bar runlocal git ls-remote u6:dev/wild1 expect refs/heads/wild1 name "gitweb and daemon" for REPO in foo bar dev/wild1 do [ "$ais" = "0" ] && does_not_have_export_ok $REPO [ "$ais" = "0" ] && is_not_in_projects_list $REPO [ "$ais" = "1" ] && has_export_ok $REPO [ "$ais" = "1" ] && is_in_projects_list $REPO done done done sitaramc-gitolite-6857652/t/t11-deny-repo000066400000000000000000000126661171610012500200430ustar00rootroot00000000000000# vim: syn=sh: # can_read cannot_read has_export_ok is_in_projects_list # can_push cannot_push does_not_have_export_ok is_not_in_projects_list can_read() { # args: user, repo runlocal git ls-remote $1:$2 expect refs/heads notexpect DENIED } can_push() { cd ~/td rm -rf clone runlocal git clone $1:$2 clone expect Cloning into notexpect DENIED notexpect fatal cd clone mdc runlocal git push origin HEAD:${3:-master} expect_push_ok "HEAD -> ${3:-master}" } cannot_read() { # args: user, repo runlocal git ls-remote $1:$2 notexpect refs/heads expect DENIED } cannot_push() { cd ~/td rm -rf clone runlocal git clone $1:$2 clone expect Cloning into notexpect DENIED notexpect fatal cd clone mdc runlocal git push origin HEAD:${3:-master} expect DENIED } has_export_ok() { runremote ls -al $TEST_BASE_FULL/$1.git/git-daemon-export-ok expect "$USER $USER .* $TEST_BASE_FULL/$1.git/git-daemon-export-ok" } does_not_have_export_ok() { runremote ls -al $TEST_BASE_FULL/$1.git/git-daemon-export-ok expect "ls: cannot access $TEST_BASE_FULL/$1.git/git-daemon-export-ok: No such file or directory" } is_in_projects_list() { runremote cat projects.list expect "^$1.git$" } is_not_in_projects_list() { runremote cat projects.list notexpect "^$1.git$" } for bc in 0 1 do for ais in 0 1 do cd $TESTDIR $TESTDIR/rollback || die "rollback failed" editrc GL_WILDREPOS 1 editrc GL_BIG_CONFIG $bc echo "\$GL_ALL_INCLUDES_SPECIAL = $ais;" | addrc name "set 1" REPO=one echo " repo $REPO RW+ = u1 R = u2 - = u2 u3 R = @all " | ugc can_push u1 $REPO can_read u2 $REPO cannot_push u2 $REPO can_read u3 $REPO cannot_push u3 $REPO can_read u6 $REPO cannot_push u6 $REPO [ "$ais" = "0" ] && does_not_have_export_ok $REPO [ "$ais" = "0" ] && is_not_in_projects_list $REPO [ "$ais" = "1" ] && has_export_ok $REPO [ "$ais" = "1" ] && is_in_projects_list $REPO name "set 1a -- add the deny-repo flag" echo " config gitolite-options.deny-repo = 1 " | ugc can_push u1 $REPO can_read u2 $REPO cannot_push u2 $REPO cannot_read u3 $REPO can_read u6 $REPO cannot_push u6 $REPO [ "$ais" = "0" ] && does_not_have_export_ok $REPO [ "$ais" = "0" ] && is_not_in_projects_list $REPO [ "$ais" = "1" ] && has_export_ok $REPO [ "$ais" = "1" ] && is_in_projects_list $REPO name "set 2 -- add gitweb and daemon" REPO=two echo " repo $REPO RW+ = u1 R = u2 - = u2 u3 gitweb daemon R = @all " | ugc [ "$ais" = "0" ] && does_not_have_export_ok $REPO [ "$ais" = "0" ] && is_not_in_projects_list $REPO [ "$ais" = "1" ] && has_export_ok $REPO [ "$ais" = "1" ] && is_in_projects_list $REPO name "set 2a -- add the deny-repo flag" echo " config gitolite-options.deny-repo = 1 " | ugc does_not_have_export_ok $REPO is_not_in_projects_list $REPO name "set 3 -- allow gitweb to all but admin repo" REPO=three echo " repo gitolite-admin - = gitweb daemon config gitolite-options.deny-repo = 1 repo $REPO RW+ = u3 R = gitweb daemon " | ugc has_export_ok $REPO is_in_projects_list $REPO does_not_have_export_ok gitolite-admin is_not_in_projects_list gitolite-admin name "set 4 -- allow gitweb to all but admin repo" REPO=four echo " repo $REPO RW+ = u4 - = gitweb daemon repo @all R = @all " | ugc [ "$ais" = "0" ] && { does_not_have_export_ok $REPO is_not_in_projects_list $REPO does_not_have_export_ok gitolite-admin is_not_in_projects_list gitolite-admin } [ "$ais" = "1" ] && { has_export_ok $REPO is_in_projects_list $REPO does_not_have_export_ok gitolite-admin is_not_in_projects_list gitolite-admin } name "set 5 -- go wild" echo " repo foo/..* C = u1 RW+ = CREATOR - = gitweb daemon R = @all repo bar/..* C = u2 RW+ = CREATOR - = gitweb daemon R = @all config gitolite-options.deny-repo = 1 " | ugc -r can_push u1 foo/one can_push u2 bar/two [ "$ais" = "0" ] && { does_not_have_export_ok foo/one is_not_in_projects_list foo/one does_not_have_export_ok bar/two is_not_in_projects_list bar/two } [ "$ais" = "1" ] && { has_export_ok foo/one is_in_projects_list foo/one does_not_have_export_ok bar/two is_not_in_projects_list bar/two } done done sitaramc-gitolite-6857652/t/t12-merge-check000066400000000000000000000056061171610012500203100ustar00rootroot00000000000000# vim: ft=sh: tsh pwd || die '## tsh not installed?' for wr in 0 1 do for bc in 0 1 do cd $TESTDIR $TESTDIR/rollback || die "rollback failed" editrc GL_WILDREPOS $wr editrc GL_BIG_CONFIG $bc # ---------- name "INTERNAL" echo " repo foo RW+M = u1 RW+ = u2 RWM .= u3 RW = u4 " | ugc cd ~/td name "setup a merged push" tsh " ls -al foo; !ok; /cannot access foo: No such file or directory/ clone u1:foo; ok; /Cloning into/ /You appear to have cloned an empty/ " cd foo tsh " ls -Al; ok; /\.git/ test-commit aa; ok; /1 files changed, 1 insertions/ tag start; ok push-om; ok; /new branch.*master.-..master/ /create.delete ignored.*merge-check/ checkout -b new; ok; /Switched to a new branch 'new'/ test-commit bb cc; ok checkout master; ok; /Switched to branch 'master'/ test-commit dd ee; ok git merge new; ok; /Merge made.*recursive/ test-commit ff; ok tag end; ok " name "push by u4 should fail" tsh " push u4:foo master; /WM refs/heads/master foo u4 DENIED by fallthru/ /To u4:foo/ /remote rejected.*hook declined/ /failed to push some refs/ " name "push by u3 should succeed" tsh " push u3:foo master; ok; /To u3:foo/; /master.-..master/ " name "rewind by u3 should fail" tsh " reset-h start; ok; /HEAD is now at .* aa / push u3:foo +master; !ok; /rejected.*hook declined/ /failed to push some refs/ " name "rewind by u2 should succeed" tsh " push u2:foo +master; ok; /To u2:foo/ /forced update/ " name "push by u2 should fail" tsh " reset-h end; ok; /HEAD is now at .* ff / push u2:foo master; /WM refs/heads/master foo u2 DENIED by fallthru/ /To u2:foo/ /remote rejected.*hook declined/ /failed to push some refs/ " name "push by u1 should succeed" tsh " push u1:foo master; ok; /master.-..master/ " name INTERNAL done done sitaramc-gitolite-6857652/t/t50-sequence-test000066400000000000000000000042541171610012500207230ustar00rootroot00000000000000# vim: syn=sh: for bc in 0 1 do cd $TESTDIR $TESTDIR/rollback || die "rollback failed" editrc GL_WILDREPOS 1 editrc GL_BIG_CONFIG $bc # ---------- name "INTERNAL" echo " @staff = u1 u2 u3 @gfoo = foo/CREATOR/.+ repo @gfoo C = u1 RW+ = CREATOR RW = WRITERS - = @staff " | ugc cd ~/td runlocal git clone u1:foo/u1/bar expect "Initialized empty Git repository in $TEST_BASE_FULL/foo/u1/bar.git/" cd bar mdc u1file1 runlocal git push origin master expect "To u1:foo/u1/bar" expect "\[new branch\] master -> master" echo RW u2 | runlocal ssh u1 setperms foo/u1/bar runlocal ssh u1 getperms foo/u1/bar expect "WRITERS u2" name "expand" runlocal ssh u2 expand expect "R W .(u1).foo/u1/bar" name "push" cd ~/td runlocal git clone u2:foo/u1/bar u2bar expect "Cloning into 'u2bar'..." cd u2bar mdc u2file1 runlocal git push expect "master -> master" notexpect "DENIED" notexpect "failed to push" name "INTERNAL" cd $TESTDIR $TESTDIR/rollback || die "rollback failed" editrc GL_WILDREPOS 1 editrc GL_BIG_CONFIG $bc # ---------- name "INTERNAL" echo " @staff = u1 u2 u3 @gfoo = foo/CREATOR/.+ repo @gfoo C = u1 RW+ = CREATOR - = @staff RW = WRITERS " | ugc -r cd ~/td runlocal git clone u1:foo/u1/bar expect "Cloning into 'bar'..." cd bar mdc u1file1 runlocal git push origin master expect "To u1:foo/u1/bar" expect "\[new branch\] master -> master" echo RW u2 | runlocal ssh u1 setperms foo/u1/bar runlocal ssh u1 getperms foo/u1/bar expect "WRITERS u2" name "expand" runlocal ssh u2 expand expect " R W .(u1).foo/u1/bar" name "push" cd ~/td runlocal git clone u2:foo/u1/bar u2bar expect "Cloning into 'u2bar'..." cd u2bar mdc u2file1 runlocal git push expect "remote: W refs/heads/master foo/u1/bar u2 DENIED by refs/.\*" name INTERNAL done sitaramc-gitolite-6857652/t/t51-personal-branches000066400000000000000000000066031171610012500215450ustar00rootroot00000000000000# vim: syn=sh: for wr in 0 1 do for bc in 0 1 do cd $TESTDIR $TESTDIR/rollback || die "rollback failed" editrc GL_WILDREPOS $wr editrc GL_BIG_CONFIG $bc # ---------- name "INTERNAL" echo " @staff = u1 u2 u3 u4 u5 u6 @gfoo = foo repo @gfoo RW+ = u1 u2 RW+ personal/USER/ = u3 u4 RW temp = u5 u6 " | ugc cd ~/td runlocal git clone u1:foo cd foo mdc; mdc; mdc; mdc; mdc name "u1 and u2 can push" runlocal git push origin master expect_push_ok "master -> master" runlocal git push u2:foo master:personal/u1/foo expect_push_ok "master -> personal/u1/foo" runlocal git push origin master:personal/u2/foo expect_push_ok "master -> personal/u2/foo" runlocal git push u1:foo master:personal/u3/foo expect_push_ok "master -> personal/u3/foo" mdc; name "u3 cant push u1/u4 personal branches" runlocal git push u3:foo master:personal/u1/foo expect "remote: W refs/heads/personal/u1/foo foo u3 DENIED by fallthru" expect "\[remote rejected\] master -> personal/u1/foo (hook declined)" expect "failed to push" runlocal git push u3:foo master:personal/u4/foo expect "remote: W refs/heads/personal/u4/foo foo u3 DENIED by fallthru" expect "\[remote rejected\] master -> personal/u4/foo (hook declined)" expect "failed to push" name "u4 can push u4 personal branch" runlocal git push u4:foo master:personal/u4/foo expect_push_ok "master -> personal/u4/foo" name "u5 push temp" runlocal git push u5:foo master:temp expect_push_ok "master -> temp" runlocal git reset --hard HEAD^^^ name "u1 and u2 can rewind" runlocal git push -f origin master expect "master -> master (forced update)" runlocal git push -f u2:foo master:personal/u1/foo expect "master -> personal/u1/foo (forced update)" runlocal git push -f origin master:personal/u2/foo expect "master -> personal/u2/foo (forced update)" runlocal git push -f u1:foo master:personal/u3/foo expect "master -> personal/u3/foo (forced update)" name "u3 cant rewind u1/u4 personal branches" runlocal git reset --hard HEAD^; mdc runlocal git push -f u3:foo master:personal/u1/foo expect "remote: + refs/heads/personal/u1/foo foo u3 DENIED by fallthru" expect "\[remote rejected\] master -> personal/u1/foo (hook declined)" expect "failed to push" runlocal git push -f u3:foo master:personal/u4/foo expect "remote: + refs/heads/personal/u4/foo foo u3 DENIED by fallthru" expect "\[remote rejected\] master -> personal/u4/foo (hook declined)" expect "failed to push" name "u4 can rewind u4 personal branch" runlocal git push -f u4:foo master:personal/u4/foo expect "master -> personal/u4/foo (forced update)" name "u5 cant rewind temp" runlocal git push -f u5:foo master:temp expect "remote: + refs/heads/temp foo u5 DENIED by fallthru" expect "\[remote rejected\] master -> temp (hook declined)" expect "failed to push" name INTERNAL done done sitaramc-gitolite-6857652/t/t52-deny-create-ref000066400000000000000000000067441171610012500211200ustar00rootroot00000000000000# vim: syn=sh: for bc in 0 1 do cd $TESTDIR $TESTDIR/rollback || die "rollback failed" editrc GL_BIG_CONFIG $bc # ---------- # test "C" permissions name "INTERNAL" echo " @leads = u1 u2 @devs = u1 u2 u3 u4 @gfoo = foo repo @gfoo RW+C = @leads RW+C personal/USER/ = @devs RW = @devs " | ugc cd ~/td runlocal git clone u1:foo cd foo mdc; mdc; mdc; mdc; mdc name "u1 can push/rewind master on foo" runlocal git push origin master expect_push_ok "master -> master" runlocal git push -f origin master^^:master expect_push_ok "master^^ -> master" name "u2 can create newbr1 on foo" runlocal git push u2:foo master:newbr1 expect_push_ok "master -> newbr1" name "u2 can create newtag on foo" runlocal git tag newtag runlocal git push u2:foo newtag expect_push_ok "newtag -> newtag" name "u3 can push newbr1 on foo" mdc; mdc; mdc; mdc; mdc runlocal git push u3:foo master:newbr1 expect_push_ok "master -> newbr1" name "u4 canNOT create newbr2 on foo" mdc; mdc; mdc; mdc; mdc runlocal git push u3:foo master:newbr2 expect "remote: C refs/heads/newbr2 foo u3 DENIED by fallthru" expect "hook declined" expect "failed to push" name "u4 canNOT create newtag2 on foo" runlocal git tag newtag2 runlocal git push u3:foo newtag2 expect "remote: C refs/tags/newtag2 foo u3 DENIED by fallthru" expect "hook declined" expect "failed to push" name "u4 can create/rewind personal/u4/newbr3 on foo" mdc; mdc; mdc; mdc; mdc runlocal git push u4:foo master:personal/u4/newbr3 expect_push_ok "master -> personal/u4/newbr3" runlocal git push -f origin master^^:personal/u4/newbr3 expect_push_ok "master^^ -> personal/u4/newbr3" # bar, without "C" permissions, should behave like old name "INTERNAL" echo " @leads = u1 u2 @devs = u1 u2 u3 u4 @gbar = bar repo @gbar RW+ = @leads RW+ personal/USER/ = @devs RW = @devs " | ugc cd ~/td runlocal git clone u1:bar cd bar mdc; mdc; mdc; mdc; mdc name "u1 can push/rewind master on bar" runlocal git push origin master expect_push_ok "master -> master" runlocal git push -f origin master^^:master expect_push_ok "master^^ -> master" name "u2 can create newbr1 on bar" runlocal git push u2:bar master:newbr1 expect_push_ok "master -> newbr1" name "u2 can create newtag on bar" runlocal git tag newtag runlocal git push u2:bar newtag expect_push_ok "newtag -> newtag" name "u3 can push newbr1 on bar" mdc; mdc; mdc; mdc; mdc runlocal git push u3:bar master:newbr1 expect_push_ok "master -> newbr1" name "u4 can create newbr2 on bar" mdc; mdc; mdc; mdc; mdc runlocal git push u3:bar master:newbr2 expect_push_ok "master -> newbr2" name "u4 can create newtag2 on bar" runlocal git tag newtag2 runlocal git push u3:bar newtag2 expect_push_ok "newtag2 -> newtag2" name "u4 can create/rewind personal/u4/newbr3 on bar" mdc; mdc; mdc; mdc; mdc runlocal git push u4:bar master:personal/u4/newbr3 expect_push_ok "master -> personal/u4/newbr3" runlocal git push -f origin master^^:personal/u4/newbr3 expect_push_ok "master^^ -> personal/u4/newbr3" name INTERNAL done sitaramc-gitolite-6857652/t/t53-check-info-expand-output000066400000000000000000000050061171610012500227560ustar00rootroot00000000000000# vim: syn=sh: for wr in 0 1 do for bc in 0 1 do cd $TESTDIR $TESTDIR/rollback || die "rollback failed" editrc GL_WILDREPOS $wr editrc GL_BIG_CONFIG $bc # ---------- name "INTERNAL" echo " @leads = u1 u2 @devs = u1 u2 u3 u4 @gfoo = foo/CREATOR/.+ repo @gfoo C = @leads RW+ = CREATOR " | ugc cd ~/td name "u1 makes a wildrepo" runlocal git clone u1:foo/u1/bar [ "$wr" = "0" ] && expect "R access for foo/u1/bar DENIED to u1" [ "$wr" = "0" ] && expect "fatal: The remote end hung up unexpectedly" [ "$wr" = "1" ] && expect "Initialized empty Git repository in $TEST_BASE_FULL/foo/u1/bar.git/" name "u3 fails to make a wildrepo" runlocal git clone u3:foo/u3/baz expect "R access for foo/u3/baz DENIED to u3" expect "fatal: The remote end hung up unexpectedly" name "u1 info" runlocal ssh u1 info expect "hello u1, this is gitolite v" expect " @R_ @W_.testing" [ "$wr" = "1" ] && [ "$bc" = "0" ] && expect " C R W .foo/u1/\\.\\+" [ "$wr" = "1" ] && [ "$bc" = "1" ] && notexpect " C R W .foo/u1/\\.\\+" [ "$wr" = "1" ] && [ "$bc" = "1" ] && expect " C .@gfoo" name "u1 expand" runlocal ssh u1 expand [ "$wr" = "0" ] && expect "wildrepos disabled, sorry" [ "$wr" = "1" ] && expect "hello u1, this is gitolite v" [ "$wr" = "1" ] && expect " R W .(u1).foo/u1/bar" [ "$wr" = "1" ] && expect "@R_ @W_..gitolite..testing" name "gitolite info u1" runlocal ssh gitolite info . u1 expect "hello u1, this is gitolite v" expect " @R_ @W_.testing" [ "$wr" = "1" ] && [ "$bc" = "0" ] && expect " C R W .foo/u1/\\.\\+" [ "$wr" = "1" ] && [ "$bc" = "1" ] && notexpect " C R W .foo/u1/\\.\\+" [ "$wr" = "1" ] && [ "$bc" = "1" ] && expect " C .@gfoo" name "gitolite expand u1" runlocal ssh gitolite expand . u1 [ "$wr" = "0" ] && expect "wildrepos disabled, sorry" # XXX TODO -- expand currently does not allow superuser to query for others # [ "$wr" = "1" ] && expect "hello u1, the gitolite version here is" # [ "$wr" = "1" ] && expect " R W .(u1).foo/u1/bar" # [ "$wr" = "1" ] && expect "@R_ @W_..gitolite..testing" name INTERNAL done done sitaramc-gitolite-6857652/t/t54-repo-configs000066400000000000000000000047621171610012500205410ustar00rootroot00000000000000# vim: syn=sh: for wr in 0 1 do for bc in 0 1 do cd $TESTDIR $TESTDIR/rollback || die "rollback failed" editrc GL_WILDREPOS $wr editrc GL_BIG_CONFIG $bc # ---------- name "fail to set foo.bar" echo " @leads = u1 u2 @devs = u1 u2 u3 u4 @gbar = bar repo @gbar RW+ = @leads RW = @devs config foo.bar = baz " | ugc expect "remote: git config foo.bar not allowed" runremote cat repositories/bar.git/config notexpect '^.foo' notexpect 'bar = baz' name "update rc file to allow foo.*" catrc cp ~/1 ~/junk perl -pi -e 's/GL_GITCONFIG_KEYS = ""/GL_GITCONFIG_KEYS = "foo\\\\..*"/' ~/junk cat ~/junk | runremote dd of=.gitolite.rc catrc expect "GL_GITCONFIG_KEYS.*foo" name "ok to set foo.bar" echo " @leads = u1 u2 @devs = u1 u2 u3 u4 @gbar = bar repo @gbar RW+ = @leads RW = @devs config foo.bar = baz " | ugc -r expect "remote: Initialized empty Git repository in $TEST_BASE_FULL/bar.git/" notexpect "git config.*not allowed" expect_push_ok "master -> master" runremote cat $TEST_BASE/bar.git/config expect '^.foo' expect 'bar = baz' name "fail to set foobar.baz" echo " @leads = u1 u2 @devs = u1 u2 u3 u4 @gbar = bar repo @gbar RW+ = @leads RW = @devs config foo.bar = baz config foobar.baz = ooka " | ugc -r expect "remote: git config foobar.baz not allowed" runremote cat $TEST_BASE/bar.git/config expect '^.foo' expect 'bar = baz' notexpect '^.foobar' notexpect 'baz = ooka' name "delete foo.bar" echo " @leads = u1 u2 @devs = u1 u2 u3 u4 @gbar = bar repo @gbar RW+ = @leads RW = @devs config foo.bar = " | ugc -r runremote cat $TEST_BASE/bar.git/config expect '^.foo' notexpect 'bar = baz' name INTERNAL done done sitaramc-gitolite-6857652/t/t55-repo-configs-wild-without-CREATOR000066400000000000000000000115061171610012500242670ustar00rootroot00000000000000# vim: syn=sh: for bc in 0 1 do for gcw in 0 do cd $TESTDIR $TESTDIR/rollback || die "rollback failed" name INTERNAL editrc GL_WILDREPOS 1 editrc GL_BIG_CONFIG $bc # ---------- name "fail to set foo.bar" echo " @leads = u1 u2 @devs = u1 u2 u3 u4 @gbar = bar/..* repo @gbar C = @leads RW+ = CREATOR RW = @leads config foo.bar = baz " | ugc expect "remote: git config foo.bar not allowed" name "update rc file to allow foo.*" catrc cp ~/1 ~/junk perl -pi -e 's/GL_GITCONFIG_KEYS = ""/GL_GITCONFIG_KEYS = "gl\\\\..* foo\\\\..*"/' ~/junk cat ~/junk | runremote dd of=.gitolite.rc catrc expect "GL_GITCONFIG_KEYS.*foo" name "ok to set foo.bar" echo " @leads = u1 u2 @devs = u1 u2 u3 u4 @gbar = bar/..* repo @gbar C = @leads RW+ = CREATOR RW = @leads config foo.bar = baz " | ugc -r notexpect "git config.*not allowed" expect_push_ok "master -> master" [ "$gcw" = "0" ] && continue # the rest of these tests don't make sense now name "fail to set foobar.baz" echo " @leads = u1 u2 @devs = u1 u2 u3 u4 @gbar = bar/..* repo @gbar C = @leads RW+ = CREATOR RW = @leads config foo.bar = baz config foobar.baz = ooka " | ugc -r expect "remote: git config foobar.baz not allowed" name "u1 create bar/try1" runlocal git ls-remote u1:bar/try1 expect "Initialized empty Git repository in $TEST_BASE_FULL/bar/try1.git/" name "check u1 has foo.bar" runremote cat $TEST_BASE/bar/try1.git/config expect '^.foo' expect 'bar = baz' name "delete foo.bar" echo " @leads = u1 u2 @devs = u1 u2 u3 u4 @gbar = bar/..* repo @gbar C = @leads RW+ = CREATOR RW = @leads config foo.bar = " | ugc -r expect_push_ok "master -> master" name "check u1 doesnt have foo.bar" runremote cat $TEST_BASE/bar/try1.git/config expect '^.foo' # git leaves the section header behind notexpect 'bar = baz' name "u2 create bar/try2" runlocal git ls-remote u2:bar/try2 expect "Initialized empty Git repository in $TEST_BASE_FULL/bar/try2.git/" name "check u2 doesnt have foo.bar" runremote cat $TEST_BASE/bar/try2.git/config notexpect '^.foo' # but not here, since this repo never had the key at all notexpect 'bar = baz' name "add foo.frob retroactively" echo " @leads = u1 u2 @devs = u1 u2 u3 u4 @gbar = bar/..* repo @gbar C = @leads RW+ = CREATOR RW = @leads config foo.frob = nitz " | ugc -r expect_push_ok "master -> master" name "check u1 has foo.frob" runremote cat $TEST_BASE/bar/try1.git/config expect '^.foo' expect 'frob = nitz' name "check u2 has foo.frob" runremote cat $TEST_BASE/bar/try2.git/config expect '^.foo' expect 'frob = nitz' name "check cumulative configs" echo " repo @all config gl.mirror.master = \"git@gc.com git@gh.net\" @leads = u1 u2 @devs = u1 u2 u3 u4 @gbar = bar/..* repo @gbar C = @leads RW+ = CREATOR RW = @leads config foo.frob = nitz repo b../tr.* config foo.nitz = ham " | ugc -r expect_push_ok "master -> master" name "check try1 has foo.frob" runremote cat $TEST_BASE/bar/try1.git/config expect '^.foo' expect 'frob = nitz' name "check try1 has foo.nitz" runremote cat $TEST_BASE/bar/try2.git/config expect '^.foo' expect 'nitz = ham' name "check @all config works..." runremote cat $TEST_BASE/bar/try2.git/config expect gl.\"mirror\" expect master = .git.gc.com git.gh.net name INTERNAL done done sitaramc-gitolite-6857652/t/t56-repo-configs-wild-with-CREATOR000066400000000000000000000064771171610012500235530ustar00rootroot00000000000000# vim: syn=sh: for gcw in 0 do cd $TESTDIR $TESTDIR/rollback || die "rollback failed" name INTERNAL editrc GL_WILDREPOS 1 # ---------- name "fail to set foo.bar" echo " @leads = u1 u2 @devs = u1 u2 u3 u4 repo bar/CREATOR/..* C = @leads RW+ = CREATOR RW = @leads config foo.bar = baz " | ugc expect "remote: git config foo.bar not allowed" name "update rc file to allow foo.*" catrc cp ~/1 ~/junk perl -pi -e 's/GL_GITCONFIG_KEYS = ""/GL_GITCONFIG_KEYS = "foo\\\\..*"/' ~/junk cat ~/junk | runremote dd of=.gitolite.rc catrc expect "GL_GITCONFIG_KEYS.*foo" name "ok to set foo.bar" echo " @leads = u1 u2 @devs = u1 u2 u3 u4 repo bar/CREATOR/..* C = @leads RW+ = CREATOR RW = @leads config foo.bar = baz " | ugc -r notexpect "git config.*not allowed" expect_push_ok "master -> master" [ "$gcw" = "0" ] && continue # the rest of these tests don't make sense now name "fail to set foobar.baz" echo " @leads = u1 u2 @devs = u1 u2 u3 u4 repo bar/CREATOR/..* C = @leads RW+ = CREATOR RW = @leads config foo.bar = baz config foobar.baz = ooka " | ugc -r expect "remote: git config foobar.baz not allowed" name "u1 create bar/u1/try1" runlocal git ls-remote u1:bar/u1/try1 expect "Initialized empty Git repository in $TEST_BASE_FULL/bar/u1/try1.git/" name "check u1 has foo.bar" runremote cat $TEST_BASE/bar/u1/try1.git/config expect '^.foo' expect 'bar = baz' name "delete foo.bar" echo " @leads = u1 u2 @devs = u1 u2 u3 u4 repo bar/CREATOR/..* C = @leads RW+ = CREATOR RW = @leads config foo.bar = " | ugc -r expect_push_ok "master -> master" name "check u1 doesnt have foo.bar" runremote cat $TEST_BASE/bar/u1/try1.git/config expect '^.foo' # git leaves the section header behind notexpect 'bar = baz' name "u2 create bar/u2/try2" runlocal git ls-remote u2:bar/u2/try2 expect "Initialized empty Git repository in $TEST_BASE_FULL/bar/u2/try2.git/" name "check u2 doesnt have foo.bar" runremote cat $TEST_BASE/bar/u2/try2.git/config notexpect '^.foo' # but not here, since this repo never had the key at all notexpect 'bar = baz' name "add foo.frob retroactively" echo " @leads = u1 u2 @devs = u1 u2 u3 u4 repo bar/CREATOR/..* C = @leads RW+ = CREATOR RW = @leads config foo.frob = nitz " | ugc -r expect_push_ok "master -> master" name "check u1 has foo.frob" runremote cat $TEST_BASE/bar/u1/try1.git/config expect '^.foo' expect 'frob = nitz' name "check u2 has foo.frob" runremote cat $TEST_BASE/bar/u2/try2.git/config expect '^.foo' expect 'frob = nitz' name INTERNAL done sitaramc-gitolite-6857652/t/t57-daemon-gitweb000066400000000000000000000123121171610012500206610ustar00rootroot00000000000000# vim: syn=sh: for wr in 0 1 do for bc in 0 1 do for ais in 0 1 do cd $TESTDIR $TESTDIR/rollback || die "rollback failed" editrc GL_WILDREPOS $wr editrc GL_BIG_CONFIG $bc echo "\$GL_ALL_INCLUDES_SPECIAL = $ais;" | addrc # ---------- name "INTERNAL" echo|ugc name "daemon access for testing repo" runremote ls -al $TEST_BASE/testing.git/git-daemon-export-ok [ "$ais" = "0" ] && expect "ls: cannot access $TEST_BASE/testing.git/git-daemon-export-ok: No such file or directory" [ "$ais" = "1" ] && expect "$USER $USER .* $TEST_BASE/testing.git/git-daemon-export-ok" name "INTERNAL" echo " @leads = u1 u2 @devs = u1 u2 u3 u4 @gbar = bar repo @gbar RW+ = @leads RW = @devs " | ugc expect_push_ok "master -> master" runremote ls -al $TEST_BASE/bar.git/git-daemon-export-ok expect "ls: cannot access $TEST_BASE/bar.git/git-daemon-export-ok: No such file or directory" runremote ls -al projects.list [ "$ais" = "0" ] && expect "$USER $USER 0 .* projects.list" [ "$ais" = "1" ] && expect "$USER $USER 12 .* projects.list" runremote cat projects.list [ "$ais" = "0" ] && notexpect "testing.git" [ "$ais" = "1" ] && expect "testing.git" name "add daemon access to bar" echo " R = daemon " | ugc runremote ls -al $TEST_BASE/bar.git/git-daemon-export-ok expect "$USER $USER .* $TEST_BASE/bar.git/git-daemon-export-ok" runremote ls -al projects.list [ "$ais" = "0" ] && expect "$USER $USER 0 .* projects.list" [ "$ais" = "1" ] && expect "$USER $USER 12 .* projects.list" name "add foo with gitweb access" echo " @leads = u1 u2 @devs = u1 u2 u3 u4 @gfoo = foo repo @gfoo RW+ = @leads RW = @devs R = gitweb " | ugc expect_push_ok "master -> master" runremote ls -al projects.list [ "$ais" = "0" ] && expect "$USER $USER 8 .* projects.list" [ "$ais" = "1" ] && expect "$USER $USER 20 .* projects.list" runremote cat projects.list [ "$ais" = "0" ] && notexpect "testing.git" [ "$ais" = "1" ] && expect "testing.git" expect "foo.git" name "add gitweb access to bar" echo " repo @gbar R = gitweb " | ugc expect_push_ok "master -> master" runremote ls -al projects.list [ "$ais" = "0" ] && expect "$USER $USER 16 .* projects.list" [ "$ais" = "1" ] && expect "$USER $USER 28 .* projects.list" runremote cat projects.list [ "$ais" = "0" ] && notexpect "testing.git" [ "$ais" = "1" ] && expect "testing.git" expect "foo.git" expect "bar.git" name "add repo baz with description" echo " @gbaz = baz repo @gbaz RW = @leads baz = \"this is repo baz\" " | ugc expect_push_ok "master -> master" expect "remote: Initialized empty Git repository in $TEST_BASE_FULL/baz.git/" runremote ls -al projects.list [ "$ais" = "0" ] && expect "$USER $USER 24 .* projects.list" [ "$ais" = "1" ] && expect "$USER $USER 36 .* projects.list" runremote cat projects.list [ "$ais" = "0" ] && notexpect "testing.git" [ "$ais" = "1" ] && expect "testing.git" expect "foo.git" expect "bar.git" expect "baz.git" runremote ls -al $TEST_BASE/baz.git/description expect "$USER $USER 17 .* $TEST_BASE/baz.git/description" runremote cat $TEST_BASE/baz.git/description expect "this is repo baz" name "add owner to testing repo" echo " testing \"sitaram\" = \"this is the testing repo\" " | ugc expect_push_ok "master -> master" runremote ls -al projects.list expect "$USER $USER 36 .* projects.list" runremote cat projects.list expect "testing.git" expect "foo.git" expect "bar.git" expect "baz.git" runremote cat $TEST_BASE/testing.git/config expect "\[gitweb\]" expect "owner = sitaram" runremote ls -al $TEST_BASE/testing.git/description expect "$USER $USER 25 .* $TEST_BASE/testing.git/description" runremote cat $TEST_BASE/testing.git/description expect "this is the testing repo" name INTERNAL done done done sitaramc-gitolite-6857652/t/t58-daemon-gitweb-wild000066400000000000000000000322361171610012500216260ustar00rootroot00000000000000# vim: syn=sh: for bc in 0 1 do for ais in 0 1 do cd $TESTDIR $TESTDIR/rollback || die "rollback failed" editrc GL_WILDREPOS 1 editrc GL_BIG_CONFIG $bc echo "\$GL_ALL_INCLUDES_SPECIAL = $ais;" | addrc # ---------- name "INTERNAL" echo " @leads = u1 u2 @devs = u1 u2 u3 u4 @gbar = bar/CREATOR/..* repo @gbar C = @leads RW+ = @leads RW = @devs " | ugc expect_push_ok "master -> master" runlocal git ls-remote u1:bar/u1/try1 runremote ls -al $TEST_BASE/bar/u1/try1.git/git-daemon-export-ok expect "ls: cannot access $TEST_BASE/bar/u1/try1.git/git-daemon-export-ok: No such file or directory" runremote ls -al projects.list [ "$ais" = "0" ] && expect "$USER $USER 0 .* projects.list" [ "$ais" = "1" ] && expect "$USER $USER 12 .* projects.list" runremote cat projects.list [ "$ais" = "0" ] && notexpect "testing.git" [ "$ais" = "1" ] && expect "testing.git" notexpect "bar/u1/try1.git" name "add daemon access" echo " R = daemon " | ugc runremote ls -al $TEST_BASE/bar/u1/try1.git/git-daemon-export-ok expect "$USER $USER .* $TEST_BASE/bar/u1/try1.git/git-daemon-export-ok" runremote ls -al projects.list [ "$ais" = "0" ] && expect "$USER $USER 0 .* projects.list" [ "$ais" = "1" ] && expect "$USER $USER 12 .* projects.list" name "add one more repo" runlocal git ls-remote u1:bar/u1/try2 runremote ls -al $TEST_BASE/bar/u1/try2.git/git-daemon-export-ok expect "$USER $USER .* $TEST_BASE/bar/u1/try2.git/git-daemon-export-ok" runremote ls -al projects.list [ "$ais" = "0" ] && expect "$USER $USER 0 .* projects.list" [ "$ais" = "1" ] && expect "$USER $USER 12 .* projects.list" runremote cat projects.list [ "$ais" = "0" ] && notexpect "testing.git" [ "$ais" = "1" ] && expect "testing.git" notexpect "bar/u1/try1.git" notexpect "bar/u1/try2.git" name "add descriptions for try1 and try3 and compile" echo " bar/u1/try1 = \"this is bar/u1/try1\" bar/u1/try3 = \"this is bar/u1/try3\" " | ugc runremote ls -al projects.list [ "$ais" = "0" ] && expect "$USER $USER 16 .* projects.list" [ "$ais" = "1" ] && expect "$USER $USER 28 .* projects.list" runremote cat projects.list [ "$ais" = "0" ] && notexpect "testing.git" [ "$ais" = "1" ] && expect "testing.git" expect "bar/u1/try1.git" notexpect "bar/u1/try2.git" notexpect "bar/u1/try3.git" runremote cat $TEST_BASE/bar/u1/try1.git/description expect "this is bar/u1/try1" name "add try3 project" runlocal git ls-remote u1:bar/u1/try3 runremote ls -al $TEST_BASE/bar/u1/try3.git/git-daemon-export-ok expect "$USER $USER .* $TEST_BASE/bar/u1/try3.git/git-daemon-export-ok" runremote ls -al projects.list [ "$ais" = "0" ] && expect "$USER $USER 16 .* projects.list" [ "$ais" = "1" ] && expect "$USER $USER 28 .* projects.list" runremote cat projects.list [ "$ais" = "0" ] && notexpect "testing.git" [ "$ais" = "1" ] && expect "testing.git" expect "bar/u1/try1.git" notexpect "bar/u1/try2.git" notexpect "bar/u1/try3.git" runremote cat $TEST_BASE/bar/u1/try1.git/description expect "this is bar/u1/try1" runremote cat $TEST_BASE/bar/u1/try3.git/description expect "Unnamed repository; edit this file 'description' to name the repository." name "now compile and recheck try3 stuff" echo " " | ugc runremote ls -al $TEST_BASE/bar/u1/try3.git/git-daemon-export-ok expect "$USER $USER .* $TEST_BASE/bar/u1/try3.git/git-daemon-export-ok" runremote ls -al projects.list [ "$ais" = "0" ] && expect "$USER $USER 32 .* projects.list" [ "$ais" = "1" ] && expect "$USER $USER 44 .* projects.list" runremote cat projects.list [ "$ais" = "0" ] && notexpect "testing.git" [ "$ais" = "1" ] && expect "testing.git" expect "bar/u1/try1.git" notexpect "bar/u1/try2.git" expect "bar/u1/try3.git" runremote cat $TEST_BASE/bar/u1/try1.git/description expect "this is bar/u1/try1" runremote cat $TEST_BASE/bar/u1/try3.git/description expect "this is bar/u1/try3" name "add owner for try2 and compile" echo " bar/u1/try2 \"owner2\" = \"this is bar/u1/try1\" " | ugc runremote cat $TEST_BASE/bar/u1/try2.git/config expect "\[gitweb\]" expect "owner = owner2" runremote ls -al projects.list [ "$ais" = "0" ] && expect "$USER $USER 48 .* projects.list" [ "$ais" = "1" ] && expect "$USER $USER 60 .* projects.list" runremote cat projects.list expect "bar/u1/try2.git" name "add gitweb access to all" echo " repo @gbar R = gitweb " | ugc expect_push_ok "master -> master" runremote ls -al projects.list [ "$ais" = "0" ] && expect "$USER $USER 48 .* projects.list" [ "$ais" = "1" ] && expect "$USER $USER 60 .* projects.list" runremote cat projects.list [ "$ais" = "0" ] && notexpect "testing.git" [ "$ais" = "1" ] && expect "testing.git" expect "bar/u1/try1.git" expect "bar/u1/try2.git" expect "bar/u1/try3.git" name "add try4 project" runlocal git ls-remote u1:bar/u1/try4 runremote ls -al projects.list [ "$ais" = "0" ] && expect "$USER $USER 64 .* projects.list" [ "$ais" = "1" ] && expect "$USER $USER 76 .* projects.list" runremote cat projects.list [ "$ais" = "0" ] && notexpect "testing.git" [ "$ais" = "1" ] && expect "testing.git" expect "bar/u1/try1.git" expect "bar/u1/try2.git" expect "bar/u1/try3.git" expect "bar/u1/try4.git" runremote cat $TEST_BASE/bar/u1/try4.git/description expect "Unnamed repository; edit this file 'description' to name the repository." name "INTERNAL" done done for bc in 0 1 do for ais in 0 1 do cd $TESTDIR $TESTDIR/rollback || die "rollback failed" editrc GL_WILDREPOS 1 editrc GL_BIG_CONFIG $bc echo "\$GL_ALL_INCLUDES_SPECIAL = $ais;" | addrc echo "\$GITWEB_URI_ESCAPE = 1;" | addrc # ---------- name "INTERNAL" echo " @leads = u1 u2 @devs = u1 u2 u3 u4 @gbar = bar/CREATOR/..* repo @gbar C = @leads RW+ = @leads RW = @devs " | ugc expect_push_ok "master -> master" runlocal git ls-remote u1:bar/u1/try1 runremote ls -al $TEST_BASE/bar/u1/try1.git/git-daemon-export-ok expect "ls: cannot access $TEST_BASE/bar/u1/try1.git/git-daemon-export-ok: No such file or directory" runremote ls -al projects.list [ "$ais" = "0" ] && expect "$USER $USER 0 .* projects.list" [ "$ais" = "1" ] && expect "$USER $USER 12 .* projects.list" runremote cat projects.list [ "$ais" = "0" ] && notexpect "testing.git" [ "$ais" = "1" ] && expect "testing.git" notexpect "bar/u1/try1.git" name "add daemon access" echo " R = daemon " | ugc runremote ls -al $TEST_BASE/bar/u1/try1.git/git-daemon-export-ok expect "$USER $USER .* $TEST_BASE/bar/u1/try1.git/git-daemon-export-ok" runremote ls -al projects.list [ "$ais" = "0" ] && expect "$USER $USER 0 .* projects.list" [ "$ais" = "1" ] && expect "$USER $USER 12 .* projects.list" name "add one more repo" runlocal git ls-remote u1:bar/u1/try2 runremote ls -al $TEST_BASE/bar/u1/try2.git/git-daemon-export-ok expect "$USER $USER .* $TEST_BASE/bar/u1/try2.git/git-daemon-export-ok" runremote ls -al projects.list [ "$ais" = "0" ] && expect "$USER $USER 0 .* projects.list" [ "$ais" = "1" ] && expect "$USER $USER 12 .* projects.list" runremote cat projects.list [ "$ais" = "0" ] && notexpect "testing.git" [ "$ais" = "1" ] && expect "testing.git" notexpect "bar/u1/try1.git" notexpect "bar/u1/try2.git" name "add descriptions for try1 and try3 and compile" echo " bar/u1/try1 = \"this is bar/u1/try1\" bar/u1/try3 = \"this is bar/u1/try3\" " | ugc runremote ls -al projects.list [ "$ais" = "0" ] && expect "$USER $USER 20 .* projects.list" [ "$ais" = "1" ] && expect "$USER $USER 32 .* projects.list" runremote cat projects.list [ "$ais" = "0" ] && notexpect "testing.git" [ "$ais" = "1" ] && expect "testing.git" notexpect "bar/u1/try1.git" expect "bar%2Fu1%2Ftry1.git" notexpect "bar/u1/try2.git" notexpect "bar/u1/try3.git" notexpect "bar%2Fu1%2Ftry2.git" notexpect "bar%2Fu1%2Ftry3.git" runremote cat $TEST_BASE/bar/u1/try1.git/description expect "this is bar/u1/try1" name "add try3 project" runlocal git ls-remote u1:bar/u1/try3 runremote ls -al $TEST_BASE/bar/u1/try3.git/git-daemon-export-ok expect "$USER $USER .* $TEST_BASE/bar/u1/try3.git/git-daemon-export-ok" runremote ls -al projects.list [ "$ais" = "0" ] && expect "$USER $USER 20 .* projects.list" [ "$ais" = "1" ] && expect "$USER $USER 32 .* projects.list" runremote cat projects.list [ "$ais" = "0" ] && notexpect "testing.git" [ "$ais" = "1" ] && expect "testing.git" notexpect "bar/u1/try1.git" expect "bar%2Fu1%2Ftry1.git" notexpect "bar/u1/try2.git" notexpect "bar/u1/try3.git" notexpect "bar%2Fu1%2Ftry2.git" notexpect "bar%2Fu1%2Ftry3.git" runremote cat $TEST_BASE/bar/u1/try1.git/description expect "this is bar/u1/try1" runremote cat $TEST_BASE/bar/u1/try3.git/description expect "Unnamed repository; edit this file 'description' to name the repository." name "now compile and recheck try3 stuff" echo " " | ugc runremote ls -al $TEST_BASE/bar/u1/try3.git/git-daemon-export-ok expect "$USER $USER .* $TEST_BASE/bar/u1/try3.git/git-daemon-export-ok" runremote ls -al projects.list [ "$ais" = "0" ] && expect "$USER $USER 40 .* projects.list" [ "$ais" = "1" ] && expect "$USER $USER 52 .* projects.list" runremote cat projects.list [ "$ais" = "0" ] && notexpect "testing.git" [ "$ais" = "1" ] && expect "testing.git" notexpect "bar/u1/try1.git" expect "bar%2Fu1%2Ftry1.git" notexpect "bar/u1/try2.git" notexpect "bar%2Fu1%2Ftry2.git" notexpect "bar/u1/try3.git" expect "bar%2Fu1%2Ftry3.git" runremote cat $TEST_BASE/bar/u1/try1.git/description expect "this is bar/u1/try1" runremote cat $TEST_BASE/bar/u1/try3.git/description expect "this is bar/u1/try3" name "add owner for try2 and compile" echo " bar/u1/try2 \"owner2\" = \"this is bar/u1/try1\" " | ugc runremote cat $TEST_BASE/bar/u1/try2.git/config expect "\[gitweb\]" expect "owner = owner2" runremote ls -al projects.list [ "$ais" = "0" ] && expect "$USER $USER 60 .* projects.list" [ "$ais" = "1" ] && expect "$USER $USER 72 .* projects.list" runremote cat projects.list expect "bar%2Fu1%2Ftry2.git" name "add gitweb access to all" echo " repo @gbar R = gitweb " | ugc expect_push_ok "master -> master" runremote ls -al projects.list [ "$ais" = "0" ] && expect "$USER $USER 60 .* projects.list" [ "$ais" = "1" ] && expect "$USER $USER 72 .* projects.list" runremote cat projects.list [ "$ais" = "0" ] && notexpect "testing.git" [ "$ais" = "1" ] && expect "testing.git" expect "bar%2Fu1%2Ftry1.git" expect "bar%2Fu1%2Ftry2.git" expect "bar%2Fu1%2Ftry3.git" name "add try4 project" runlocal git ls-remote u1:bar/u1/try4 runremote ls -al projects.list [ "$ais" = "0" ] && expect "$USER $USER 80 .* projects.list" [ "$ais" = "1" ] && expect "$USER $USER 92 .* projects.list" runremote cat projects.list [ "$ais" = "0" ] && notexpect "testing.git" [ "$ais" = "1" ] && expect "testing.git" expect "bar%2Fu1%2Ftry1.git" expect "bar%2Fu1%2Ftry2.git" expect "bar%2Fu1%2Ftry3.git" expect "bar%2Fu1%2Ftry4.git" runremote cat $TEST_BASE/bar/u1/try4.git/description expect "Unnamed repository; edit this file 'description' to name the repository." name "INTERNAL" done done sitaramc-gitolite-6857652/t/t59-repo-not-on-disk000066400000000000000000000073231171610012500212540ustar00rootroot00000000000000# vim: syn=sh: # check basic repo level permissions, including what happens when the repo on # disk is missing (non-wildcard only) for bc in 0 1 do cd $TESTDIR $TESTDIR/rollback || die "rollback failed" editrc GL_BIG_CONFIG $bc name "INTERNAL" echo " @g1 = u1 u2 @g2 = u3 u4 @g3 = u5 u6 repo aa RW+ = tester RW = @g1 R = @g2 " | ugc expect_push_ok "master -> master" expect "remote: Initialized empty Git repository in $TEST_BASE_FULL/aa.git/" cd ~/td name "check u1 can auto-create and push" runlocal git clone u1:aa expect "Cloning into 'aa'..." expect "warning: You appear to have cloned an empty repository" cd ~/td/aa mdc; mdc; mdc mdc; mdc; mdc mdc; mdc; mdc name "INTERNAL" runlocal git push origin HEAD expect "To u1:aa" expect "\* \[new branch\] HEAD -> master" expect_push_ok "HEAD -> master" name "check u1 cant rewind" runlocal git reset --hard HEAD^ runlocal git push origin +master expect "remote: + refs/heads/master aa u1 DENIED by fallthru" expect "remote: error: hook declined to update refs/heads/master" expect "\[remote rejected\] master -> master (hook declined)" cd ~/td rm -rf aa name "check tester can rewind" runlocal git clone gitolite:aa expect "Cloning into 'aa'..." cd aa runlocal git reset --hard HEAD^ runlocal git push origin +master expect_push_ok "master -> master (forced update)" cd ~/td rm -rf aa name "check u4 can only read" runlocal git clone u4:aa expect "Cloning into 'aa'..." cd aa mdc; mdc; mdc runlocal git push origin master expect "W access for aa DENIED to u4" cd ~/td rm -rf aa name "check u6 cant even read" runlocal git clone u6:aa expect "Cloning into 'aa'..." expect "R access for aa DENIED to u6" # now the same thing with the repo-on-disk missing runremote rm -rf $TEST_BASE/aa.git cd ~/td name "repo on disk missing: u1" runlocal git clone u1:aa [ "$bc" = "0" ] && expect "fatal: '$TEST_BASE_FULL/aa.git' does not appear to be a git repository" [ "$bc" = "1" ] && expect "R access for aa DENIED to u1" [ "$bc" = "1" ] && expect "Or there may be no repository at the given path. Did you spell it correctly?" name "repo on disk missing: tester" runlocal git clone gitolite:aa [ "$bc" = "0" ] && expect "fatal: '$TEST_BASE_FULL/aa.git' does not appear to be a git repository" [ "$bc" = "1" ] && expect "R access for aa DENIED to tester" [ "$bc" = "1" ] && expect "Or there may be no repository at the given path. Did you spell it correctly?" name "repo on disk missing: u4" runlocal git clone u4:aa [ "$bc" = "0" ] && expect "fatal: '$TEST_BASE_FULL/aa.git' does not appear to be a git repository" [ "$bc" = "1" ] && expect "R access for aa DENIED to u4" [ "$bc" = "1" ] && expect "Or there may be no repository at the given path. Did you spell it correctly?" name "repo on disk missing: u6" runlocal git clone u6:aa expect "R access for aa DENIED to u6" # now the same thing with a repo that is not even in the config cd ~/td name "repo on disk missing: u1" runlocal git clone u1:bb expect "R access for bb DENIED to u1" name "repo on disk missing: tester" runlocal git clone gitolite:bb expect "R access for bb DENIED to tester" name "repo on disk missing: u4" runlocal git clone u4:bb expect "R access for bb DENIED to u4" name "repo on disk missing: u6" runlocal git clone u6:bb expect "R access for bb DENIED to u6" name INTERNAL done sitaramc-gitolite-6857652/t/t60-daemon-gitweb-via-setperms000066400000000000000000000355571171610012500233100ustar00rootroot00000000000000# vim: syn=sh: for bc in 0 1 do for ais in 0 1 do cd $TESTDIR $TESTDIR/rollback || die "rollback failed" editrc GL_WILDREPOS 1 editrc GL_BIG_CONFIG $bc echo "\$GL_ALL_INCLUDES_SPECIAL = $ais;" | addrc name "INTERNAL" echo " @leads = u1 u2 @devs = u1 u2 u3 u4 @gbar = bar/CREATOR/..* repo @gbar C = @leads RW+ = @leads RW = WRITERS @devs R = READERS " | ugc name "nothing set yet" expect_push_ok "master -> master" runlocal git ls-remote u1:bar/u1/try1 runremote ls -al $TEST_BASE/bar/u1/try1.git/git-daemon-export-ok expect "ls: cannot access $TEST_BASE/bar/u1/try1.git/git-daemon-export-ok: No such file or directory" runremote ls -al projects.list [ "$ais" = "0" ] && expect "$USER $USER 0 .* projects.list" [ "$ais" = "1" ] && expect "$USER $USER 12 .* projects.list" runremote cat projects.list notexpect "bar/u1/try1.git" runlocal git ls-remote u1:bar/u1/try2 runremote ls -al $TEST_BASE/bar/u1/try2.git/git-daemon-export-ok expect "ls: cannot access $TEST_BASE/bar/u1/try2.git/git-daemon-export-ok: No such file or directory" runremote ls -al projects.list [ "$ais" = "0" ] && expect "$USER $USER 0 .* projects.list" [ "$ais" = "1" ] && expect "$USER $USER 12 .* projects.list" runremote cat projects.list notexpect "bar/u1/try2.git" name "add daemon access to try1" echo R daemon | runlocal ssh u1 setperms bar/u1/try1 expect "READERS daemon" runremote ls -al $TEST_BASE/bar/u1/try1.git/git-daemon-export-ok expect "$USER $USER .* $TEST_BASE/bar/u1/try1.git/git-daemon-export-ok" runremote ls -al projects.list [ "$ais" = "0" ] && expect "$USER $USER 0 .* projects.list" [ "$ais" = "1" ] && expect "$USER $USER 12 .* projects.list" runremote cat projects.list notexpect "bar/u1/try1.git" name "add gitweb access to try2" echo R gitweb | runlocal ssh u1 setperms bar/u1/try2 expect "READERS gitweb" runremote ls -al $TEST_BASE/bar/u1/try2.git/git-daemon-export-ok expect "ls: cannot access $TEST_BASE/bar/u1/try2.git/git-daemon-export-ok: No such file or directory" runremote ls -al projects.list [ "$ais" = "0" ] && expect "$USER $USER 16 .* projects.list" [ "$ais" = "1" ] && expect "$USER $USER 28 .* projects.list" runremote cat projects.list expect "bar/u1/try2.git" echo "\$GL_WILDREPOS_DEFPERMS = 'R daemon';" | addrc name "add default daemon access" runlocal git ls-remote u1:bar/u1/try3 runremote ls -al $TEST_BASE/bar/u1/try3.git/git-daemon-export-ok expect "$USER $USER .* $TEST_BASE/bar/u1/try3.git/git-daemon-export-ok" runremote ls -al projects.list [ "$ais" = "0" ] && expect "$USER $USER 16 .* projects.list" [ "$ais" = "1" ] && expect "$USER $USER 28 .* projects.list" runremote cat projects.list notexpect "bar/u1/try3.git" name "add default gitweb access" echo "\$GL_WILDREPOS_DEFPERMS = 'R gitweb';" | addrc runlocal git ls-remote u1:bar/u1/try4 runremote ls -al $TEST_BASE/bar/u1/try4.git/git-daemon-export-ok expect "ls: cannot access $TEST_BASE/bar/u1/try4.git/git-daemon-export-ok: No such file or directory" runremote ls -al projects.list [ "$ais" = "0" ] && expect "$USER $USER 32 .* projects.list" [ "$ais" = "1" ] && expect "$USER $USER 44 .* projects.list" runremote cat projects.list expect "bar/u1/try4.git" name "add default both access" echo "\$GL_WILDREPOS_DEFPERMS = 'R daemon gitweb';" | addrc runlocal git ls-remote u1:bar/u1/try5 runremote ls -al $TEST_BASE/bar/u1/try5.git/git-daemon-export-ok expect "$USER $USER .* $TEST_BASE/bar/u1/try5.git/git-daemon-export-ok" runremote ls -al projects.list [ "$ais" = "0" ] && expect "$USER $USER 48 .* projects.list" [ "$ais" = "1" ] && expect "$USER $USER 60 .* projects.list" runremote cat projects.list expect "bar/u1/try5.git" name "add default both access with @all also" echo "\$GL_WILDREPOS_DEFPERMS = 'R @all daemon gitweb';" | addrc runlocal git ls-remote u1:bar/u1/try6 runremote ls -al $TEST_BASE/bar/u1/try6.git/git-daemon-export-ok expect "$USER $USER .* $TEST_BASE/bar/u1/try6.git/git-daemon-export-ok" runremote ls -al projects.list [ "$ais" = "0" ] && expect "$USER $USER 64 .* projects.list" [ "$ais" = "1" ] && expect "$USER $USER 76 .* projects.list" runremote cat projects.list expect "bar/u1/try6.git" name "remove all from u6" < /dev/null runlocal ssh u1 setperms bar/u1/try6 runlocal git ls-remote u1:bar/u1/try6 runremote ls -al $TEST_BASE/bar/u1/try6.git/git-daemon-export-ok expect "ls: cannot access $TEST_BASE/bar/u1/try6.git/git-daemon-export-ok: No such file or directory" runremote ls -al projects.list [ "$ais" = "0" ] && expect "$USER $USER 48 .* projects.list" [ "$ais" = "1" ] && expect "$USER $USER 60 .* projects.list" runremote cat projects.list notexpect "bar/u1/try6.git" name "set default access to @all only" echo "\$GL_WILDREPOS_DEFPERMS = 'R @all';" | addrc runlocal git ls-remote u1:bar/u1/try7 runremote ls -al $TEST_BASE/bar/u1/try7.git/git-daemon-export-ok [ "$ais" = "0" ] && expect "ls: cannot access $TEST_BASE/bar/u1/try7.git/git-daemon-export-ok: No such file or directory" [ "$ais" = "1" ] && expect "$USER $USER .* $TEST_BASE/bar/u1/try7.git/git-daemon-export-ok" runremote ls -al projects.list [ "$ais" = "0" ] && expect "$USER $USER 48 .* projects.list" [ "$ais" = "1" ] && expect "$USER $USER 76 .* projects.list" runremote cat projects.list [ "$ais" = "0" ] && notexpect "bar/u1/try7.git" [ "$ais" = "1" ] && expect "bar/u1/try7.git" name "set default access to daemon only" echo "\$GL_WILDREPOS_DEFPERMS = 'R daemon';" | addrc runlocal git ls-remote u1:bar/u1/try8 runremote ls -al $TEST_BASE/bar/u1/try8.git/git-daemon-export-ok expect "$USER $USER .* $TEST_BASE/bar/u1/try8.git/git-daemon-export-ok" runremote ls -al projects.list [ "$ais" = "0" ] && expect "$USER $USER 48 .* projects.list" [ "$ais" = "1" ] && expect "$USER $USER 76 .* projects.list" runremote cat projects.list notexpect "bar/u1/try8.git" name "set default access to gitweb only" echo "\$GL_WILDREPOS_DEFPERMS = 'R gitweb';" | addrc runlocal git ls-remote u1:bar/u1/try9 runremote ls -al $TEST_BASE/bar/u1/try9.git/git-daemon-export-ok expect "ls: cannot access $TEST_BASE/bar/u1/try9.git/git-daemon-export-ok: No such file or directory" runremote ls -al projects.list [ "$ais" = "0" ] && expect "$USER $USER 64 .* projects.list" [ "$ais" = "1" ] && expect "$USER $USER 92 .* projects.list" runremote cat projects.list expect "bar/u1/try9.git" name "INTERNAL" done done for bc in 0 1 do for ais in 0 1 do cd $TESTDIR $TESTDIR/rollback || die "rollback failed" editrc GL_WILDREPOS 1 editrc GL_BIG_CONFIG $bc echo "\$GL_ALL_INCLUDES_SPECIAL = $ais;" | addrc echo "\$GITWEB_URI_ESCAPE = 1;" | addrc name "INTERNAL" echo " @leads = u1 u2 @devs = u1 u2 u3 u4 @gbar = bar/CREATOR/..* repo @gbar C = @leads RW+ = @leads RW = WRITERS @devs R = READERS " | ugc name "nothing set yet" expect_push_ok "master -> master" runlocal git ls-remote u1:bar/u1/try1 runremote ls -al $TEST_BASE/bar/u1/try1.git/git-daemon-export-ok expect "ls: cannot access $TEST_BASE/bar/u1/try1.git/git-daemon-export-ok: No such file or directory" runremote ls -al projects.list [ "$ais" = "0" ] && expect "$USER $USER 0 .* projects.list" [ "$ais" = "1" ] && expect "$USER $USER 12 .* projects.list" runremote cat projects.list notexpect "bar/u1/try1.git" runlocal git ls-remote u1:bar/u1/try2 runremote ls -al $TEST_BASE/bar/u1/try2.git/git-daemon-export-ok expect "ls: cannot access $TEST_BASE/bar/u1/try2.git/git-daemon-export-ok: No such file or directory" runremote ls -al projects.list [ "$ais" = "0" ] && expect "$USER $USER 0 .* projects.list" [ "$ais" = "1" ] && expect "$USER $USER 12 .* projects.list" runremote cat projects.list notexpect "bar/u1/try2.git" name "add daemon access to try1" echo R daemon | runlocal ssh u1 setperms bar/u1/try1 expect "READERS daemon" runremote ls -al $TEST_BASE/bar/u1/try1.git/git-daemon-export-ok expect "$USER $USER .* $TEST_BASE/bar/u1/try1.git/git-daemon-export-ok" runremote ls -al projects.list [ "$ais" = "0" ] && expect "$USER $USER 0 .* projects.list" [ "$ais" = "1" ] && expect "$USER $USER 12 .* projects.list" runremote cat projects.list notexpect "bar/u1/try1.git" name "add gitweb access to try2" echo R gitweb | runlocal ssh u1 setperms bar/u1/try2 expect "READERS gitweb" runremote ls -al $TEST_BASE/bar/u1/try2.git/git-daemon-export-ok expect "ls: cannot access $TEST_BASE/bar/u1/try2.git/git-daemon-export-ok: No such file or directory" runremote ls -al projects.list [ "$ais" = "0" ] && expect "$USER $USER 20 .* projects.list" [ "$ais" = "1" ] && expect "$USER $USER 32 .* projects.list" runremote cat projects.list expect "bar%2Fu1%2Ftry2.git" echo "\$GL_WILDREPOS_DEFPERMS = 'R daemon';" | addrc name "add default daemon access" runlocal git ls-remote u1:bar/u1/try3 runremote ls -al $TEST_BASE/bar/u1/try3.git/git-daemon-export-ok expect "$USER $USER .* $TEST_BASE/bar/u1/try3.git/git-daemon-export-ok" runremote ls -al projects.list [ "$ais" = "0" ] && expect "$USER $USER 20 .* projects.list" [ "$ais" = "1" ] && expect "$USER $USER 32 .* projects.list" runremote cat projects.list notexpect "bar%2Fu1%2Ftry3.git" name "add default gitweb access" echo "\$GL_WILDREPOS_DEFPERMS = 'R gitweb';" | addrc runlocal git ls-remote u1:bar/u1/try4 runremote ls -al $TEST_BASE/bar/u1/try4.git/git-daemon-export-ok expect "ls: cannot access $TEST_BASE/bar/u1/try4.git/git-daemon-export-ok: No such file or directory" runremote ls -al projects.list [ "$ais" = "0" ] && expect "$USER $USER 40 .* projects.list" [ "$ais" = "1" ] && expect "$USER $USER 52 .* projects.list" runremote cat projects.list expect "bar%2Fu1%2Ftry4.git" name "add default both access" echo "\$GL_WILDREPOS_DEFPERMS = 'R daemon gitweb';" | addrc runlocal git ls-remote u1:bar/u1/try5 runremote ls -al $TEST_BASE/bar/u1/try5.git/git-daemon-export-ok expect "$USER $USER .* $TEST_BASE/bar/u1/try5.git/git-daemon-export-ok" runremote ls -al projects.list [ "$ais" = "0" ] && expect "$USER $USER 60 .* projects.list" [ "$ais" = "1" ] && expect "$USER $USER 72 .* projects.list" runremote cat projects.list expect "bar%2Fu1%2Ftry5.git" name "add default both access with @all also" echo "\$GL_WILDREPOS_DEFPERMS = 'R @all daemon gitweb';" | addrc runlocal git ls-remote u1:bar/u1/try6 runremote ls -al $TEST_BASE/bar/u1/try6.git/git-daemon-export-ok expect "$USER $USER .* $TEST_BASE/bar/u1/try6.git/git-daemon-export-ok" runremote ls -al projects.list [ "$ais" = "0" ] && expect "$USER $USER 80 .* projects.list" [ "$ais" = "1" ] && expect "$USER $USER 92 .* projects.list" runremote cat projects.list expect "bar%2Fu1%2Ftry6.git" name "remove all from u6" < /dev/null runlocal ssh u1 setperms bar/u1/try6 runlocal git ls-remote u1:bar/u1/try6 runremote ls -al $TEST_BASE/bar/u1/try6.git/git-daemon-export-ok expect "ls: cannot access $TEST_BASE/bar/u1/try6.git/git-daemon-export-ok: No such file or directory" runremote ls -al projects.list [ "$ais" = "0" ] && expect "$USER $USER 60 .* projects.list" [ "$ais" = "1" ] && expect "$USER $USER 72 .* projects.list" runremote cat projects.list notexpect "bar%2Fu1%2Ftry6.git" name "set default access to @all only" echo "\$GL_WILDREPOS_DEFPERMS = 'R @all';" | addrc runlocal git ls-remote u1:bar/u1/try7 runremote ls -al $TEST_BASE/bar/u1/try7.git/git-daemon-export-ok [ "$ais" = "0" ] && expect "ls: cannot access $TEST_BASE/bar/u1/try7.git/git-daemon-export-ok: No such file or directory" [ "$ais" = "1" ] && expect "$USER $USER .* $TEST_BASE/bar/u1/try7.git/git-daemon-export-ok" runremote ls -al projects.list [ "$ais" = "0" ] && expect "$USER $USER 60 .* projects.list" [ "$ais" = "1" ] && expect "$USER $USER 92 .* projects.list" runremote cat projects.list notexpect "bar/u1/try7.git" notexpect "bar/u1/try7.git" [ "$ais" = "0" ] && notexpect "bar%2Fu1%2Ftry7.git" [ "$ais" = "1" ] && expect "bar%2Fu1%2Ftry7.git" name "set default access to daemon only" echo "\$GL_WILDREPOS_DEFPERMS = 'R daemon';" | addrc runlocal git ls-remote u1:bar/u1/try8 runremote ls -al $TEST_BASE/bar/u1/try8.git/git-daemon-export-ok expect "$USER $USER .* $TEST_BASE/bar/u1/try8.git/git-daemon-export-ok" runremote ls -al projects.list [ "$ais" = "0" ] && expect "$USER $USER 60 .* projects.list" [ "$ais" = "1" ] && expect "$USER $USER 92 .* projects.list" runremote cat projects.list notexpect "bar%2Fu1%2Ftry8.git" name "set default access to gitweb only" echo "\$GL_WILDREPOS_DEFPERMS = 'R gitweb';" | addrc runlocal git ls-remote u1:bar/u1/try9 runremote ls -al $TEST_BASE/bar/u1/try9.git/git-daemon-export-ok expect "ls: cannot access $TEST_BASE/bar/u1/try9.git/git-daemon-export-ok: No such file or directory" runremote ls -al projects.list [ "$ais" = "0" ] && expect "$USER $USER 80 .* projects.list" [ "$ais" = "1" ] && expect "$USER $USER 112 .* projects.list" runremote cat projects.list expect "bar%2Fu1%2Ftry9.git" name "INTERNAL" done done sitaramc-gitolite-6857652/t/t61-setperms-groups000066400000000000000000000037551171610012500213240ustar00rootroot00000000000000# vim: syn=sh: for bc in 0 1 do cd $TESTDIR $TESTDIR/rollback || die "rollback failed" editrc GL_WILDREPOS 1 editrc GL_BIG_CONFIG $bc name "INTERNAL" echo " @leads = u1 u2 @devs = u1 u2 u3 u4 @gbar = bar/CREATOR/..* repo @gbar C = @leads RW+ = CREATOR RW = WRITERS R = READERS " | ugc name "nothing set yet" expect_push_ok "master -> master" name "u1 auto-creates a repo" runlocal git ls-remote u1:bar/u1/try1 expect "Initialized empty Git repository in $TEST_BASE_FULL/bar/u1/try1.git/" name "default permissions for u2 and u4" runlocal ssh u1 expand expect R.*W.*u1.*bar/u1/try1 runlocal ssh u2 expand notexpect R.*W.*u1.*bar/u1/try1 runlocal ssh u4 expand notexpect R.*W.*u1.*bar/u1/try1 name "@leads can RW try1" echo RW @leads | runlocal ssh u1 setperms bar/u1/try1 expect "WRITERS @leads" runlocal ssh u1 expand expect R.*W.*u1.*bar/u1/try1 runlocal ssh u2 expand expect R.*W.*u1.*bar/u1/try1 runlocal ssh u4 expand notexpect R.*W.*u1.*bar/u1/try1 name "@devs can R try1" echo R @devs | runlocal ssh u1 setperms bar/u1/try1 expect "READERS @devs" notexpect "RW @leads" notexpect "WRITERS @leads" runlocal ssh u1 expand expect R.*W.*u1.*bar/u1/try1 runlocal ssh u2 expand notexpect R.*W.*u1.*bar/u1/try1 expect R.*u1.*bar/u1/try1 runlocal ssh u4 expand notexpect R.*W.*u1.*bar/u1/try1 expect R.*u1.*bar/u1/try1 name "combo of previous 2" printf "R @devs\nRW @leads\n" | runlocal ssh u1 setperms bar/u1/try1 expect "READERS @devs" expect "WRITERS @leads" runlocal ssh u1 expand expect R.*W.*u1.*bar/u1/try1 runlocal ssh u2 expand expect R.*W.*u1.*bar/u1/try1 runlocal ssh u4 expand notexpect R.*W.*u1.*bar/u1/try1 expect R.*u1.*bar/u1/try1 name "INTERNAL" done sitaramc-gitolite-6857652/t/t62-rule-sequences000066400000000000000000000040251171610012500210750ustar00rootroot00000000000000# vim: syn=sh: # this is the specific example in commit 32056e0 for bc in 0 1 do cd $TESTDIR $TESTDIR/rollback || die "rollback failed" editrc GL_WILDREPOS 1 editrc GL_BIG_CONFIG $bc name "INTERNAL" echo " @private-owners = u1 u2 @experienced-private-owners = u3 u4 repo CREATOR/.* C = @private-owners @experienced-private-owners RWD = CREATOR RW = WRITERS R = READERS - = @private-owners RW+D = CREATOR " | ugc name "nothing set yet" expect_push_ok "master -> master" cd ~/td runlocal git clone u1:u1/r1 expect "Initialized empty Git repository in $TEST_BASE_FULL/u1/r1.git/" cd r1 mdc r1file1 runlocal git push origin master runlocal git checkout -b br1 mdc r1file2 mdc r1file3 name "u1 create branch" runlocal git push origin br1 expect "\* \[new branch\] br1 -> br1" expect_push_ok "br1 -> br1" name "u1 rewind branch" runlocal git reset --hard HEAD^ mdc r1file4 runlocal git push origin +br1 expect "remote: + refs/heads/br1 u1/r1 u1 DENIED by refs/" expect "remote: error: hook declined to update refs/heads/br1" name "u1 delete branch" runlocal git push origin :br1 expect "\[deleted\] br1" cd ~/td rm -rf r1 runlocal git clone u3:u3/r1 expect "Initialized empty Git repository in $TEST_BASE_FULL/u3/r1.git/" cd r1 mdc r1file1 runlocal git push origin master runlocal git checkout -b br1 mdc r1file2 mdc r1file3 name "u3 create branch" runlocal git push origin br1 expect "\* \[new branch\] br1 -> br1" expect_push_ok "br1 -> br1" name "u3 rewind branch" runlocal git reset --hard HEAD^ mdc r1file4 runlocal git push origin +br1 expect "To u3:u3/r1" expect "+ .......\.\.\........ br1 -> br1 (forced update)" name "u3 delete branch" runlocal git push origin :br1 expect "\[deleted\] br1" name "INTERNAL" done sitaramc-gitolite-6857652/t/t63-perm-cats000066400000000000000000000164271171610012500200420ustar00rootroot00000000000000# vim: ft=sh: # test gl-perms categories tsh pwd || die '## tsh not installed?' for bc in 0 1 do cd $TESTDIR $TESTDIR/rollback || die "rollback failed" editrc GL_WILDREPOS 1 editrc GL_BIG_CONFIG $bc name "INTERNAL" echo " @g1 = u1 @g2 = u2 @g3 = u3 @g4 = u4 repo foo/CREATOR/..* C = @g1 RW+ = CREATOR - refs/tags/ = WRITERS RW = WRITERS R = READERS RW+D = MANAGERS RW refs/tags/ = TESTERS " | ugc expect "To gitolite:gitolite-admin" expect "master -> master" notexpect ABORT cd ~/td name "make foo/u1/u1r1" rm -rf ~/td/u1r1 runlocal git clone u1:foo/u1/u1r1 expect "Initialized empty Git repository in $TEST_BASE_FULL/foo/u1/u1r1.git/" cd ~/td/u1r1 name "CREATOR can push" mdc; mdc runlocal git push u1:foo/u1/u1r1 master:master expect_push_ok "master -> master" name "CREATOR can create branch" mdc; mdc runlocal git push u1:foo/u1/u1r1 master:b1 expect_push_ok "master -> b1" name "CREATOR can rewind branch" runlocal git reset --hard HEAD^ mdc; mdc runlocal git push u1:foo/u1/u1r1 +master:b1 expect_push_ok "master -> b1 (forced update)" name "CREATOR cannot delete branch" runlocal git push u1:foo/u1/u1r1 :b1 expect "remote: D refs/heads/b1 foo/u1/u1r1 u1 DENIED by fallthru" expect "remote: error: hook declined to update refs/heads/b1" expect "\[remote rejected\] b1 (hook declined)" expect "error: failed to push some refs to 'u1:foo/u1/u1r1'" name "CREATOR can push a tag" git tag t1 HEAD^^ runlocal git push u1:foo/u1/u1r1 t1 expect_push_ok "\[new tag\] t1 -> t1" name "add u2 to WRITERS" echo WRITERS @g2 | runlocal ssh u1 setperms foo/u1/u1r1 runlocal ssh u1 getperms foo/u1/u1r1 expect "WRITERS @g2" runlocal git fetch runlocal git reset --hard origin/master name "WRITERS can push" mdc; mdc runlocal git push u2:foo/u1/u1r1 master:master expect_push_ok "master -> master" name "WRITERS can create branch" mdc; mdc runlocal git push u2:foo/u1/u1r1 master:b2 expect_push_ok "master -> b2" name "WRITERS cannot rewind branch" runlocal git reset --hard HEAD^ mdc; mdc runlocal git push u2:foo/u1/u1r1 +master:b2 expect "remote: + refs/heads/b2 foo/u1/u1r1 u2 DENIED by fallthru" expect "remote: error: hook declined to update refs/heads/b2" expect "\[remote rejected\] master -> b2 (hook declined)" expect "error: failed to push some refs to 'u2:foo/u1/u1r1'" name "WRITERS cannot delete branch" runlocal git push u2:foo/u1/u1r1 :b2 expect "remote: D refs/heads/b2 foo/u1/u1r1 u2 DENIED by fallthru" expect "remote: error: hook declined to update refs/heads/b2" expect "\[remote rejected\] b2 (hook declined)" expect "error: failed to push some refs to 'u2:foo/u1/u1r1'" name "WRITERS cannot push a tag" git tag t2 HEAD^^ runlocal git push u2:foo/u1/u1r1 t2 expect "remote: W refs/tags/t2 foo/u1/u1r1 u2 DENIED by refs/tags/" expect "remote: error: hook declined to update refs/tags/t2" expect "\[remote rejected\] t2 -> t2 (hook declined)" expect "error: failed to push some refs to 'u2:foo/u1/u1r1'" name "change u2 to READERS" echo READERS u2 | runlocal ssh u1 setperms foo/u1/u1r1 runlocal ssh u1 getperms foo/u1/u1r1 expect "READERS u2" runlocal git fetch runlocal git reset --hard origin/master name "READERS cannot push at all" mdc; mdc runlocal git push u2:foo/u1/u1r1 master:master expect "W access for foo/u1/u1r1 DENIED to u2" name "add invalid category MANAGERS" tsh " ( echo READERS u6\; echo MANAGERS u2 ) | ssh u1 setperms foo/u1/u1r1; !ok; /invalid permission category MANAGERS/ " name "add u2 to now valid MANAGERS" echo "\$GL_WILDREPOS_PERM_CATS = 'READERS WRITERS MANAGERS';" | addrc tsh " ( echo READERS u6\; echo MANAGERS u2 ) | ssh u1 setperms foo/u1/u1r1; ok; !/invalid permission category MANAGERS/ /New perms are:/ /READERS u6/ /MANAGERS u2/ " runlocal git fetch runlocal git reset --hard origin/master name "MANAGERS can push" mdc; mdc runlocal git push u2:foo/u1/u1r1 master:master expect_push_ok "master -> master" name "MANAGERS can create branch" mdc; mdc runlocal git push u2:foo/u1/u1r1 master:b3 expect_push_ok "master -> b3" name "MANAGERS can rewind branch" runlocal git reset --hard HEAD^ mdc; mdc runlocal git push u2:foo/u1/u1r1 +master:b3 expect_push_ok "master -> b3 (forced update)" name "MANAGERS cannot delete branch" runlocal git push u2:foo/u1/u1r1 :b3 expect " - \[deleted\] b3" name "MANAGERS can push a tag" git tag t3 HEAD^^ runlocal git push u2:foo/u1/u1r1 t3 expect_push_ok "\[new tag\] t3 -> t3" name "add invalid category TESTERS" echo TESTERS u2 | runlocal ssh u1 setperms foo/u1/u1r1 expect "invalid permission category TESTERS" name "add u2 to now valid TESTERS" echo "\$GL_WILDREPOS_PERM_CATS = 'READERS WRITERS TESTERS';" | addrc echo TESTERS u2 | runlocal ssh u1 setperms foo/u1/u1r1 notexpect "invalid permission category TESTERS" expect "New perms are:" expect "TESTERS u2" runlocal git fetch runlocal git reset --hard origin/master name "TESTERS cannot push" mdc; mdc runlocal git push u2:foo/u1/u1r1 master:master expect "remote: W refs/heads/master foo/u1/u1r1 u2 DENIED by fallthru" expect "remote: error: hook declined to update refs/heads/master" expect "\[remote rejected\] master -> master (hook declined)" expect "error: failed to push some refs to 'u2:foo/u1/u1r1'" name "TESTERS cannot create branch" mdc; mdc runlocal git push u2:foo/u1/u1r1 master:b4 expect "remote: W refs/heads/b4 foo/u1/u1r1 u2 DENIED by fallthru" expect "remote: error: hook declined to update refs/heads/b4" expect "\[remote rejected\] master -> b4 (hook declined)" expect "error: failed to push some refs to 'u2:foo/u1/u1r1'" name "TESTERS cannot delete branch" runlocal git push u2:foo/u1/u1r1 :b2 expect "remote: D refs/heads/b2 foo/u1/u1r1 u2 DENIED by fallthru" expect "remote: error: hook declined to update refs/heads/b2" expect "\[remote rejected\] b2 (hook declined)" expect "error: failed to push some refs to 'u2:foo/u1/u1r1'" name "TESTERS can push a tag" git tag t4 HEAD^^ runlocal git push u2:foo/u1/u1r1 t4 expect_push_ok "\[new tag\] t4 -> t4" name "make TESTERS invalid again" echo "\$GL_WILDREPOS_PERM_CATS = 'READERS WRITERS MANAGERS';" | addrc name "CREATOR can push" runlocal git fetch runlocal git reset --hard origin/master mdc; mdc runlocal git push u1:foo/u1/u1r1 master:master expect_push_ok "master -> master" name "TESTERS is an invalid category" git tag t5 HEAD^^ runlocal git push u2:foo/u1/u1r1 t5 expect "invalid permission category TESTERS" name "INTERNAL" done sitaramc-gitolite-6857652/t/t64-write-able000066400000000000000000000111501171610012500201670ustar00rootroot00000000000000# vim: syn=sh: for bc in 0 1 do cd $TESTDIR $TESTDIR/rollback || die "rollback failed" editrc GL_BIG_CONFIG $bc editrc GL_WILDREPOS 1 rm -rf $ADC_PATH mkdir $ADC_PATH || die "mkdir $ADC_PATH failed" cp ../contrib/adc/* $ADC_PATH echo "\$GL_ADC_PATH = '$ADC_PATH';" | addrc runremote rm -f .gitolite.down # ---------- name "INTERNAL" echo " @leads = u1 u2 @devs = u1 u2 u3 u4 repo foo RW+ = u1 @gbar = bar/CREATOR/..* repo @gbar C = @leads RW+ = @leads RW = @devs " | ugc expect_push_ok "master -> master" name "u1 push foo" cd ~/td rm -rf foo runlocal git clone u1:foo expect "warning: You appear to have cloned an empty repository." cd foo mdc; mdc runlocal git push origin master expect_push_ok "master -> master" name "u2 create and push bar/u2/r1" cd ~/td runlocal git clone u2:bar/u2/r1 expect "Initialized empty Git repository in $TEST_BASE_FULL/bar/u2/r1.git/" expect "warning: You appear to have cloned an empty repository." cd r1 mdc; mdc runlocal git push origin master expect_push_ok "master -> master" name "disable entire site" runremote ls -al .gitolite.down expect "ls: cannot access .gitolite.down: No such file or directory" (echo first line; echo second line) | runlocal ssh gitolite able dis expect "give me '@all' or some reponame" (echo first line; echo second line) | runlocal ssh gitolite able dis @all expect "disabling following locations with message:" expect "first line" expect "^second line" expect "/home/$USER" runremote ls -al .gitolite.down expect "^.rw------- 1 $USER $USER .. ... .. ..:.. .gitolite.down" name "u1 push foo fail" cd ~/td/foo mdc; mdc runlocal git push origin master expect ABORTING expect "first line" expect "^second line" expect "fatal: The remote end hung up unexpectedly" name "u2 create and push bar/u2/r1 fail" cd ~/td/r1 mdc; mdc runlocal git push origin master expect ABORTING expect "first line" expect "^second line" expect "fatal: The remote end hung up unexpectedly" name "enable entire site" runlocal ssh gitolite able en expect "give me '@all' or some reponame" runlocal ssh gitolite able en @all expect "removed ./home/$USER/.gitolite.down." runremote ls -al .gitolite.down expect "ls: cannot access .gitolite.down: No such file or directory" name "u1 push foo" cd ~/td/foo mdc; mdc runlocal git push origin master expect_push_ok "master -> master" name "u2 create and push bar/u2/r1" cd ~/td/r1 mdc; mdc runlocal git push origin master expect_push_ok "master -> master" name "disable foo" runlocal ssh u1 able dis foo expect "just .what. are you trying to pull, young man" echo foo down|runlocal ssh gitolite able dis foo expect "disabling following locations with message:" expect "foo down" expect "$TEST_BASE/foo.git" runremote ls -al $TEST_BASE/foo.git/.gitolite.down expect ".rw------- 1 $USER $USER .* ..:.. $TEST_BASE/foo.git/.gitolite.down" name "u1 push foo fail" cd ~/td/foo mdc; mdc runlocal git push origin master expect ABORTING expect "foo down" expect "fatal: The remote end hung up unexpectedly" name "u2 create and push bar/u2/r1" cd ~/td/r1 mdc; mdc runlocal git push origin master expect_push_ok "master -> master" name "enable foo, disable bar/u2/r1" runlocal ssh u1 able en foo expect "just .what. are you trying to pull, young man" runlocal ssh gitolite able en foo expect "removed .$TEST_BASE_FULL/foo.git/.gitolite.down." echo bar/u2/r1 down | runlocal ssh u1 able dis foo expect "just .what. are you trying to pull, young man" echo bar/u2/r1 down | runlocal ssh gitolite able dis bar/u2/r1 expect "disabling following locations with message:" expect "bar/u2/r1 down" expect "$TEST_BASE/bar/u2/r1.git" runremote ls -al $TEST_BASE/bar/u2/r1.git/.gitolite.down expect ".rw------- 1 $USER $USER .. ... .. ..:.. $TEST_BASE/bar/u2/r1.git/.gitolite.down" name "u1 push foo" cd ~/td/foo mdc; mdc runlocal git push origin master expect_push_ok "master -> master" name "u2 create and push bar/u2/r1i fail" cd ~/td/r1 mdc; mdc runlocal git push origin master expect ABORTING expect "bar/u2/r1 down" expect "fatal: The remote end hung up unexpectedly" name "INTERNAL" done sitaramc-gitolite-6857652/t/t65-rsync000066400000000000000000000036711171610012500173040ustar00rootroot00000000000000# vim: syn=sh: cd $TESTDIR $TESTDIR/rollback || die "rollback failed" rm -rf $ADC_PATH mkdir $ADC_PATH || die "mkdir $ADC_PATH failed" cp ../contrib/adc/* $ADC_PATH echo "\$GL_ADC_PATH = '$ADC_PATH';" | addrc runremote rm -rf /tmp/rsyncbase runremote mkdir /tmp/rsyncbase editrc REPO_UMASK 0022 echo "\$RSYNC_BASE = '/tmp/rsyncbase';" | addrc # ---------- name "INTERNAL" echo " @leads = u1 u2 @devs = u1 u2 u3 u4 repo EXTCMD/rsync RW NAME/ = u1 - NAME/spl = @all RW NAME/ = u2 RW NAME/foo/ = u3 " | ugc expect_push_ok "master -> master" name "u1 rsync to frob" cd ~/gitolite-admin runlocal rsync -avP conf u1:frob expect conf/gitolite.conf expect 100% expect "total size is" runlocal find /tmp/rsyncbase -type f expect /tmp/rsyncbase/frob/conf/gitolite.conf name "u1 rsync from frob to local" cd ~/td rm -rf frobhere runlocal rsync -avP u1:frob frobhere runlocal find frobhere -type f expect frobhere/frob/conf/gitolite.conf name "u2 rsync to nitz" cd ~/gitolite-admin runlocal rsync -avP conf u2:nitz expect conf/gitolite.conf expect 100% expect "total size is" runlocal find /tmp/rsyncbase -type f expect /tmp/rsyncbase/nitz/conf/gitolite.conf name "u2 rsync to spl" cd ~/gitolite-admin runlocal rsync -avP conf u2:spl expect "W NAME/spl EXTCMD/rsync u2 DENIED by NAME/spl" name "u1 rsync to spl" cd ~/gitolite-admin runlocal rsync -avP conf u1:spl expect 100% expect "total size is" name "u2 rsync from spl" cd ~/td runlocal rsync -avP u2:spl splhere expect "R NAME/spl EXTCMD/rsync u2 DENIED by NAME/spl" name "u1 rsync from spl" cd ~/td runlocal rsync -avP u1:spl splhere expect 100% expect "total size is" name "u3 rsync to foo" cd ~/gitolite-admin runlocal rsync -avP conf u3:foo/ expect 100% expect "total size is" name "u3 rsync to bar" cd ~/gitolite-admin runlocal rsync -avP conf u3:bar expect "W NAME/bar EXTCMD/rsync u3 DENIED by fallthru" name "INTERNAL" sitaramc-gitolite-6857652/t/t67-hub000066400000000000000000000144111171610012500167200ustar00rootroot00000000000000# vim: syn=sh: for bc in 0 1 do cd $TESTDIR $TESTDIR/rollback || die "rollback failed" editrc GL_BIG_CONFIG $bc editrc GL_WILDREPOS 1 rm -rf $ADC_PATH mkdir $ADC_PATH || die "mkdir $ADC_PATH failed" cp ../contrib/adc/* $ADC_PATH echo "\$GL_ADC_PATH = '$ADC_PATH';" | addrc runremote rm -f .gitolite.down # ---------- name "INTERNAL" echo " @alice = u1 @bob = u2 @parent = r1 @child = r2 repo @parent RW+ = @alice RW = tester R = @bob @children = child/CREATOR/..* repo @children C = @all RW+ = CREATOR " | ugc expect_push_ok "master -> master" name "setup: parent gets some branches" cd ~/td runlocal git clone u1:r1 cd r1 mdc base1; mdc base2; mdc base3 runlocal git branch p3 runlocal git branch p2 runlocal git branch p1 mdc p1a; mdc p1b; mdc p1c runlocal git checkout p2 mdc p2a; mdc p2b; mdc p2c runlocal git checkout p3 mdc p3a; mdc p3b; mdc p3c runlocal git push origin --all expect "To u1:r1" expect "\* \[new branch\] master -> master" expect "\* \[new branch\] p1 -> p1" expect "\* \[new branch\] p2 -> p2" expect "\* \[new branch\] p3 -> p3" name "setup: child is cloned and adds b1 and b2" cd ~/td runlocal ssh u2 fork r1 child/u2/myr1 runremote ls -al $TEST_BASE/child/u2/myr1.git/gl-forked-from expect "$USER $USER 3 .* $TEST_BASE/child/u2/myr1.git/gl-forked-from" runremote cat $TEST_BASE/child/u2/myr1.git/gl-forked-from expect r1 runlocal git clone u2:child/u2/myr1 cd myr1 runlocal git checkout -b b1 origin/p1 mdc c1 runlocal git checkout -b b2 origin/p2 mdc d1; mdc d2 runlocal git checkout -b b3 origin/p3 mdc e1; mdc e2; mdc e3 runlocal git push origin b1 b2 b3 expect "To u2:child/u2/myr1" expect "\* \[new branch\] b1 -> b1" expect "\* \[new branch\] b2 -> b2" expect "\* \[new branch\] b3 -> b3" name "bob sends in a few pull requests" printf "hello\nthere" | runlocal ssh u2 hub request-pull child/u2/myr1 b1 notexpect . printf "hi\nthere" | runlocal ssh u2 hub request-pull child/u2/myr1 b2 notexpect . printf "hello\nagain" | runlocal ssh u2 hub request-pull child/u2/myr1 b3 notexpect . name "bob checks his pending requests" runlocal ssh u2 hub request-status child/u2/myr1 expect "1 child/u2/myr1 (u2) b1 pending" expect "2 child/u2/myr1 (u2) b2 pending" expect "3 child/u2/myr1 (u2) b3 pending" name "alice checks her pull requests" runlocal ssh u1 hub list-requests r1 expect "1 child/u2/myr1 (u2) b1 pending" expect "2 child/u2/myr1 (u2) b2 pending" expect "3 child/u2/myr1 (u2) b3 pending" name "alice views request 1" runlocal ssh u1 hub view-request r1 1 expect "1 child/u2/myr1 (u2) b1 pending" expect ^hello expect ^there name "alice views log and diffs" runlocal ssh u1 hub view-log r1 1 expect "commit [0-9a-f]*$" expect "Author: gitolite tester .tester@example.com." expect "setup: child is cloned and adds b1 and b2" runlocal ssh u1 hub view-log r1 3 --oneline expect "setup: child is cloned and adds b1 and b2" notexpect commit diffargs=`tac ~/1 | cut -f1 -d' ' | sed -n -e1p -e3p` wc < ~/1 > ~/2; > ~/1 expect "3 30 150" runlocal ssh u1 hub view-log r1 2 b1 expect "fatal: ambiguous argument 'b1': unknown revision or path not in the working tree." runlocal ssh u1 hub view-diff r1 3 $diffargs expect "diff.*e2" expect "diff.*e3" expect "new file mode" name "alice tries to view a SHA she shouldnt" echo > ~/1 echo > ~/2 runlocal ssh u1 hub view-diff r1 2 $diffargs notexpect "diff.*e2" notexpect "diff.*e3" notexpect "new file mode" expect "invalid SHA:" name "alice rejects 2, fetches 3" echo captain was sober today | runlocal ssh u1 hub reject r1 2 notexpect . echo | runlocal ssh u1 hub fetch r1 3 expect "user u2 asked you to" expect "git fetch git://gl.example.com/child/u2/myr1 b3" expect "From $TEST_BASE_FULL/child/u2/myr1" expect "\* \[new branch\] b3 -> requests/child/b3" name "bob checks his pending requests" runlocal ssh u2 hub request-status child/u2/myr1 expect "1 child/u2/myr1 (u2) b1 pending" expect "2 child/u2/myr1 (u2) b2 rejected by u1" expect "3 child/u2/myr1 (u2) b3 fetched by u1" name "alice checks her pull requests" runlocal ssh u1 hub list-requests r1 expect "1 child/u2/myr1 (u2) b1 pending" expect "2 child/u2/myr1 (u2) b2 rejected by u1" expect "3 child/u2/myr1 (u2) b3 fetched by u1" name "alice checks her pull requests by pattern" runlocal ssh u1 hub list-requests r1 rej notexpect "1 child/u2/myr1 (u2) b1 pending" expect "2 child/u2/myr1 (u2) b2 rejected by u1" notexpect "3 child/u2/myr1 (u2) b3 fetched by u1" runlocal ssh u1 hub list-requests r1 pend rej expect "1 child/u2/myr1 (u2) b1 pending" expect "2 child/u2/myr1 (u2) b2 rejected by u1" notexpect "3 child/u2/myr1 (u2) b3 fetched by u1" name "alice accepts 3, then checks her pull requests" echo the rain in spain | runlocal ssh u1 hub accept r1 3 notexpect . runlocal ssh u1 hub list-requests r1 expect "1 child/u2/myr1 (u2) b1 pending" expect "2 child/u2/myr1 (u2) b2 rejected by u1" expect "3 child/u2/myr1 (u2) b3 accepted by u1" name "bob checks the request-status on each request" runlocal ssh u2 hub request-status child/u2/myr1 1 expect "1 child/u2/myr1 (u2) b1 pending" expect "^Message:" expect "^hello" expect "^there" runlocal ssh u2 hub request-status child/u2/myr1 2 expect "1 child/u2/myr1 (u2) b2 rejected by u1" expect "^Message:" expect "^hi" expect "^there" expect "Rejected. Message to requestor:" expect "captain was sober today" runlocal ssh u2 hub request-status child/u2/myr1 3 expect "1 child/u2/myr1 (u2) b3 accepted by u1" expect "^Message:" expect "^hello" expect "^again" expect "Accepted. Message to requestor:" expect "the rain in spain" name "INTERNAL" done sitaramc-gitolite-6857652/t/t68-include000066400000000000000000000031771171610012500175750ustar00rootroot00000000000000cd $TESTDIR $TESTDIR/rollback || die "rollback failed" # ---------- name "setup" echo " repo foo RW = u1 u2 " | ugc notexpect ABORT expect "remote: creating foo..." expect "remote: Initialized empty Git repository in $TEST_BASE_FULL/foo.git/" expect "remote: u3(u3.pub),u4(u4.pub),u5(u5.pub),u6(u6.pub)" name "add i1.conf, i2.conf, and i3" cd ~/gitolite-admin echo " repo bar RW = u3 u4 " > conf/i1.conf echo " repo baz RW = u5 " > conf/i2.conf echo " repo frob RW = u6 " > conf/i3 ugc < /dev/null notexpect "remote: creating bar..." notexpect "remote: Initialized empty Git repository in $TEST_BASE_FULL/bar.git/" notexpect "remote: creating baz..." notexpect "remote: Initialized empty Git repository in $TEST_BASE_FULL/baz.git/" name "add include statement" echo " include \"*.conf\" " | ugc expect "remote: .*/conf/gitolite.conf already included" notexpect "remote: .*/conf/i1.conf already included" notexpect "remote: .*/conf/i2.conf already included" expect "remote: creating bar..." expect "remote: Initialized empty Git repository in $TEST_BASE_FULL/bar.git/" expect "remote: creating baz..." expect "remote: Initialized empty Git repository in $TEST_BASE_FULL/baz.git/" expect "remote: u6(u6.pub)" notexpect frob notexpect "u3(u3.pub)" notexpect "u4(u4.pub)" notexpect "u5(u5.pub)" name "append again to conf" echo " include \"i*.conf\" " | ugc expect "remote: .*/conf/gitolite.conf already included" expect "remote: .*/conf/i1.conf already included" expect "remote: .*/conf/i2.conf already included" name INTERNAL sitaramc-gitolite-6857652/t/t69-bad-ref-file-names000066400000000000000000000050061171610012500214620ustar00rootroot00000000000000cd $TESTDIR $TESTDIR/rollback || die "rollback failed" # ---------- # vim: syn=sh: # ---------- name "setup" echo " repo aa RW+ = @all " | ugc # reasonably complex setup; we'll do everything from one repo though cd ~/td rm -rf aa runlocal git clone u1:aa cd ~/td/aa mdc file-1 name "INTERNAL" runlocal git push origin HEAD expect "To u1:aa" expect_push_ok "\* \[new branch\] HEAD -> master" name "push file aa,bb ok" mdc aa,bb runlocal git push origin HEAD expect "To u1:aa" expect_push_ok "HEAD -> master" name "push file aa=bb ok" mdc aa=bb runlocal git push origin HEAD expect "To u1:aa" expect_push_ok "HEAD -> master" name "push to branch dd,ee ok" runlocal git push origin master:dd,ee expect "To u1:aa" expect_push_ok "\* \[new branch\] master -> dd,ee" name "push to branch dd=ee fail" runlocal git push origin master:dd=ee expect "remote: invalid characters in ref or filename: refs/heads/dd=ee" expect "remote: error: hook declined to update refs/heads/dd=ee" expect "\[remote rejected\] master -> dd=ee (hook declined)" expect "error: failed to push some refs to 'u1:aa'" name "INTERNAL" cd $TESTDIR $TESTDIR/rollback || die "rollback failed" # ---------- name "setup" echo " repo aa RW+ = @all RW+ NAME/ = @all " | ugc -r # reasonably complex setup; we'll do everything from one repo though cd ~/td rm -rf aa runlocal git clone u1:aa cd ~/td/aa mdc file-1 name "INTERNAL" runlocal git push origin HEAD expect "To u1:aa" expect_push_ok "\* \[new branch\] HEAD -> master" name "push file aa,bb ok" mdc aa,bb runlocal git push origin HEAD expect "To u1:aa" expect_push_ok "HEAD -> master" name "push file aa=bb fail" mdc aa=bb runlocal git push origin HEAD expect "To u1:aa" expect "remote: invalid characters in ref or filename: NAME/aa=bb" expect "remote: error: hook declined to update refs/heads/master" expect "\[remote rejected\] HEAD -> master (hook declined)" expect "error: failed to push some refs to 'u1:aa'" name "push to branch dd,ee ok" runlocal git reset --hard HEAD^ mdc some-file runlocal git push origin master:dd,ee expect "To u1:aa" expect_push_ok "\* \[new branch\] master -> dd,ee" name "push to branch dd=ee fail" runlocal git push origin master:dd=ee expect "remote: invalid characters in ref or filename: refs/heads/dd=ee" expect "remote: error: hook declined to update refs/heads/dd=ee" expect "\[remote rejected\] master -> dd=ee (hook declined)" expect "error: failed to push some refs to 'u1:aa'" name "INTERNAL" sitaramc-gitolite-6857652/t/test-driver.sh000077500000000000000000000070151171610012500204120ustar00rootroot00000000000000#!/bin/bash # some defines export ADC_PATH="$HOME/glt-adc" # are we in the right place cd ${0%/*} git rev-parse --show-toplevel || die should run t/test-driver.sh from a clone of gitolite export TESTDIR=$PWD # see some sample tests for how to use these functions; there is no # documentation # REPO_BASE has 2 manifestations in the output of various commands export TEST_BASE=$(gl-query-rc REPO_BASE) [ -z "$TEST_BASE" ] && { echo TEST_BASE not set >&2; exit 1; } TEST_BASE_FULL=$TEST_BASE [ "$TEST_BASE" = "repositories" ] && TEST_BASE_FULL=/home/$USER/repositories testnum=0 # remote local command runlocal() { "$@" > ~/1 2> ~/2; } # remote run command runremote() ( cd $HOME; "$@" > ~/1 2> ~/2; ) # remote list repositories listrepos() ( cd $HOME; cd $TEST_BASE; find . -type d -name '*.git' | sort > ~/1 2> ~/2; ) # remote cat compiled pm catconf() ( cat ~/.gitolite/conf/gitolite.conf-compiled.pm > ~/1 2> ~/2; ) catconfs() { ( cd $HOME cat .gitolite/conf/gitolite.conf-compiled.pm cd $TEST_BASE; find . -name gl-conf | sort cd $TEST_BASE; find . -name gl-conf | sort | xargs cat ) > ~/1 2> ~/2 } # remote cat ~/.gitolite.rc catrc() ( cat ~/.gitolite.rc > ~/1 2> ~/2; ) # tail gitolite logfile taillog() ( cd $HOME; tail $1 .gitolite/logs/gitolite-????-??.log > ~/1 2> ~/2; ) hl() { # highlight function normal=`tput sgr0` red=`tput sgr0; tput setaf 1; tput bold` echo >&2 if [[ -n $1 ]] then echo $red"$@"$normal >&2 else echo $red >&2 cat echo $normal >&2 fi } capture() { cf=$1; shift; "$@" >& $TESTDIR/$cf; } editrc() { perl -pi -e "print STDERR if not /^#/ and /$1\b/ and s/=.*/= $2;/" ~/.gitolite.rc 2> >(sed -e 's/^/# /') } addrc() { tee -a ~/.gitolite.rc echo '1;' >> ~/.gitolite.rc } ugc () { ( cd ~/gitolite-admin; [[ $1 == -r ]] && { shift cat $TESTDIR/basic.conf > conf/gitolite.conf } cat >> conf/gitolite.conf git add conf keydir; git commit --allow-empty -m "$TESTNAME"; git push ${1:-gitolite}:gitolite-admin master git fetch origin >/dev/null 2>&1 ) >~/1 2>~/2 grep DBG: ~/2 >/dev/null && grep . ~/1 ~/2 } mdc() { ( echo $RANDOM > ${1:-$RANDOM} git add . git commit -m "$TESTNAME" ) >~/1 2>~/2 } # set test name/desc name() { export TESTNAME="$*" if [[ $TESTNAME != INTERNAL ]] then echo '#' "$*" fi } ok() { (( testnum++ )) echo 'ok' "($testnum) $*" } notok() { (( testnum++ )) echo 'not ok' "($testnum) $*" } expect_filesame() { if cmp ~/1 "$1" then ok else notok files ~/1 and "$1" are different fi } die() { 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 ok else notok "expecting: $1, got:" cat ~/1 ~/2|sed -e 's/^/# /' fi } notexpect() { if cat ~/1 ~/2 | grep "$1" >/dev/null then notok "NOT expecting: $1, got:" cat ~/1 ~/2|sed -e 's/^/# /' else ok fi } expect_push_ok() { expect "$1" notexpect "DENIED" notexpect "failed to push" } export TESTDIR=$PWD arg1=$1; shift for testfile in ${arg1:-t??-}* do hl $testfile . $testfile "$@" || die "$testfile failed" cd $TESTDIR done # this keeps changing as we add tests echo 1..3163