Compare commits

...

No commits in common. "master" and "v1.5.6" have entirely different histories.

241 changed files with 13056 additions and 13603 deletions

4
.gitattributes vendored Normal file
View file

@ -0,0 +1,4 @@
conf/* crlf=input
src/* crlf=input
hooks/common/* crlf=input
hooks/gitolite-admin/* crlf=input

5
.gitignore vendored Normal file
View file

@ -0,0 +1,5 @@
*.tar
*.tgz
*.tar.gz
*.tar.bz2
conf/VERSION

View file

@ -1,86 +0,0 @@
2012-12-29 v3.3 bug fix: gl-perms propagation to slaves broke sometime
after v3.2 (so if you're only picking up tagged releases
you're OK)
the "D" command now allows rm/unlock to be totally
disabled
new trigger: update-gitweb-daemon-from-options; another
way to update gitweb and daemon access lists
new 'create' command for explicit wild repo creation, and
new AutoCreate trigger to control auto-creation
allow simple macros in conf file
2012-11-14 v3.2 major efficiency boost for large setups
optional support for multi-line pubkeys; see
src/triggers/post-compile/ssh-authkeys-split
bug fix for not creating gl-conf when repo para has only
config lines and no access rules
new 'bg' trigger command to put long jobs started from a
trigger into background
%GL_REPO and %GL_CREATOR now work for 'option's also
test suite now much more BSD friendly
2012-10-05 v3.1 (security) fix path traversal on wild repos
new %GL_CREATOR variable for git-config lines
rsync command to create and send bundles automagically
migrated 'who-pushed'
logical expressions on refexes!!!
2012-06-27 v3.04 documentation graduated and moved out of parents house :)
new trigger for 'repo specific umask'
new 'list-dangling-repos' command
new LOCAL_CODE rc var; allow admin specified programs to
override system-installed ones
new 'upstream' trigger-cum-command to maintain local
copies of external repos
new 'sudo' command
minor backward compat breakage in 'gitolite query-rc'
'perms' command can now create repo if needed
migrated 'symbolic-ref' command
'gitolite setup --hooks-only'
2012-05-23 v3.03 fix major bug that allowed an admin to get a shell
2012-05-20 v3.02 packaging instructions fixed up and smoke tested
make it easier to give some users a full shell
allow aliasing a repo to another name
simulate POST_CREATE for new normal (non-wild) repos
(just for kicks) a VREF that allows for voting on changes
to a branch
bug fix: smart http was not running PRE_ and POST_GIT
triggers
htpasswd migrated
2012-04-29 v3.01 mostly BSD and Solaris compat
also fork command added
2012-04-18 v3.0 first release to "master"
This is a compete rewrite of gitolite; please see
documentation before upgrading.

21
Makefile Normal file
View file

@ -0,0 +1,21 @@
# 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

359
README.md
View file

@ -1,359 +0,0 @@
Github-users: click the 'wiki' link before sending me anything via github.
Existing users: this is gitolite v3.x. If you are upgrading from v2.x this
file will not suffice; you *must* check the online docs (see below for URL).
------------------------------------------------------------------------
This file contains BASIC DOCUMENTATION ONLY.
* It is suitable for a fresh, ssh-based, installation of gitolite and basic
usage of its most important features.
* It is NOT meant to be exhaustive or detailed.
The COMPLETE DOCUMENTATION is at:
http://sitaramc.github.com/gitolite/master-toc.html
Please go there for what/why/how, concepts, background, troubleshooting, more
details on what is covered here, or advanced features not covered here.
------------------------------------------------------------------------
BASIC DOCUMENTATION FOR GITOLITE
================================
This file contains the following sections:
* INSTALLATION AND SETUP
* ADDING USERS AND REPOS
* HELP FOR YOUR USERS
* BASIC SYNTAX
* ACCESS RULES
* GROUPS
* COMMANDS
* THE 'rc' FILE
* GIT-CONFIG
* GIT-DAEMON
* GITWEB
* CONTACT AND SUPPORT
* LICENSE
------------------------------------------------------------------------
INSTALLATION AND SETUP
----------------------
Server requirements:
* any unix system
* sh
* git 1.6.6+
* perl 5.8.8+
* openssh 5.0+
* a dedicated userid to host the repos (in this document, we assume it
is 'git'), with shell access ONLY by 'su - git' from some other userid
on the same server.
Steps to install:
* login as 'git' as described above
* make sure ~/.ssh/authorized_keys is empty or non-existent
* make sure your ssh public key from your workstation is available at $HOME/YourName.pub
* run the following commands:
git clone git://github.com/sitaramc/gitolite
mkdir -p $HOME/bin
gitolite/install -to $HOME/bin
gitolite setup -pk YourName.pub
If the last command doesn't run perhaps 'bin' in not in your 'PATH'.
You can either add it, or just run:
$HOME/bin/gitolite setup -pk YourName.pub
ADDING USERS AND REPOS
----------------------
Do NOT add new repos or users manually on the server. Gitolite users,
repos, and access rules are maintained by making changes to a special repo
called 'gitolite-admin' and pushing those changes to the server.
----
To administer your gitolite installation, start by doing this on your
workstation (if you have not already done so):
git clone git@host:gitolite-admin
**NOTE**: if you are asked for a password, something has gone wrong.
Now if you 'cd gitolite-admin', you will see two subdirectories in it:
'conf' and 'keydir'.
To add new users alice, bob, and carol, obtain their public keys and add
them to 'keydir' as alice.pub, bob.pub, and carol.pub respectively.
To add a new repo 'foo' and give different levels of access to these
users, edit the file 'conf/gitolite.conf' and add lines like this:
repo foo
RW+ = alice
RW = bob
R = carol
See the 'ACCESS RULES' section later for more details.
Once you have made these changes, do something like this:
git add conf
git add keydir
git commit -m 'added foo, gave access to alice, bob, carol'
git push
When the push completes, gitolite will add the new users to
~/.ssh/authorized_keys on the server, as well as create a new, empty, repo
called 'foo'.
HELP FOR YOUR USERS
-------------------
Once a user has sent you their public key and you have added them as
specified above and given them access, you have to tell them what URL to
access their repos at. This is usually 'git clone git@host:reponame'; see
man git-clone for other forms.
**NOTE**: again, if they are asked for a password, something is wrong.
If they need to know what repos they have access to, they just have to run
'ssh git@host info'; see 'COMMANDS' section later for more on this.
BASIC SYNTAX
------------
The basic syntax of the conf file is very simple.
* Everything is space separated; there are no commas, semicolons, etc.,
in the syntax.
* Comments are in the usual perl/shell style.
* User 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.
* There are no continuation lines.
ACCESS RULES
------------
This section is mostly 'by example'.
Gitolite's access rules are very powerful. The simplest use was already
shown above. Here is a slightly more detailed example:
repo foo
RW+ = alice
- master = bob
- refs/tags/v[0-9] = bob
RW = bob
RW refs/tags/v[0-9] = carol
R = dave
For clones and fetches, as long as the user is listed with an R, RW
or RW+ in at least one rule, he is allowed to read the repo.
For pushes, rules are processed in sequence until a rule is found
where the user, the permission (see note 1), and the refex (note 2)
*all* match. At that point, if the permission on the matched rule
was '-', the push is denied, otherwise it is allowed. If no rule
matches, the push is denied.
Note 1: permission matching:
* a permission of RW matches only a fast-forward push or create
* a permission of RW+ matches any type of push
* a permission of '-' matches any type of push
Note 2: refex matching:
(refex = optional regex to match the ref being pushed)
* an empty refex is treated as 'refs/.*'
* a refex that does not start with 'refs/' is prefixed with 'refs/heads/'
* finally, a '^' is prefixed
* the ref being pushed is matched against this resulting refex
With all that background, here's what the example rules say:
* alice can do anything to any branch or tag -- create, push, delete, rewind/overwrite etc.
* bob can create or fast-forward push any branch whose name does
not start with 'master' and create any tag whose name does not
start with 'v'+digit.
* carol can create tags whose names start with 'v'+digit.
* dave can clone/fetch.
GROUPS
------
Gitolite allows you to group users or repos for convenience. Here's an
example that creates two groups of users:
@staff = alice bob carol
@interns = ashok
repo secret
RW = @staff
repo foss
RW+ = @staff
RW = @interns
Group lists accumulate. The following two lines have the same effect as
the earlier definition of @staff above:
@staff = alice bob
@staff = carol
You can also use group names in other group names:
@all-devs = @staff @interns
Finally, @all is a special group name that is often convenient to use if
you really mean 'all repos' or 'all users'.
COMMANDS
--------
Users can run certain commands remotely, using ssh. For example:
ssh git@host help
prints a list of available commands.
The most commonly used command is 'info'. All commands respond to a
single argument of '-h' with suitable information.
If you have shell on the server, you have a lot more commands available to
you; try running 'gitolite help'.
THE 'rc' FILE
--------------
Some of the instructions below may require you to edit the rc file
(~/.gitolite.rc on the server).
The rc file is perl code, but you do NOT need to know perl to edit it.
Just mind the commas, use single quotes unless you know what you're doing,
and make sure the brackets and braces stay matched up.
GIT-CONFIG
----------
Gitolite lets you set git-config values for individual repos without
having to log on to the server and run 'git config' commands:
repo foo
config hooks.mailinglist = foo-commits@example.tld
config hooks.emailprefix = '[foo] '
config foo.bar = ''
config foo.baz =
**WARNING**
The last syntax shown above is the *only* way to *delete* a config
variable once you have added it. Merely removing it from the conf
file will *not* delete it from the repo.git/config file.
**SECURITY NOTE**
Some git-config keys allow arbitrary code to be run on the server.
If all of your gitolite admins already have shell access to the server
account hosting it, you can edit the rc file (~/.gitolite.rc) on the
server, and change the GIT_CONFIG_KEYS line to look like this:
GIT_CONFIG_KEYS => '.*',
Otherwise, give it a space-separated list of regular expressions that
define what git-config keys are allowed. For example, this one allows
only variables whose names start with 'gitweb' or with 'gc' to be
defined:
GIT_CONFIG_KEYS => 'gitweb\..* gc\..*',
GIT-DAEMON
----------
Gitolite creates the 'git-daemon-export-ok' file for any repo that is
readable by a special user called 'daemon', like so:
repo foo
R = daemon
GITWEB
------
Any repo that is readable by a special user called 'gitweb' will be added
to the projects.list file.
repo foo
R = gitweb
Or you can set one or more of the following config variables instead:
repo foo
config gitweb.owner = some person's name
config gitweb.description = some description
config gitweb.category = some category
**NOTE**
You will probably need to change the UMASK in the rc file from the
default (0077) to 0027 and add whatever user your gitweb is running as
to the 'git' group. After that, you need to run a one-time 'chmod -R'
on the already created files and directories.
------------------------------------------------------------------------
CONTACT AND SUPPORT
-------------------
Mailing list for support and general discussion:
gitolite@googlegroups.com
subscribe address: gitolite+subscribe@googlegroups.com
Mailing list for announcements and notices:
subscribe address: gitolite-announce+subscribe@googlegroups.com
IRC: #git and #gitolite on freenode. Note that I live in India (UTC+0530
time zone).
Author: sitaramc@gmail.com, but please DO NOT use this for general support
questions. Subscribe to the list and ask there instead.
LICENSE
-------
The gitolite *code* is released under GPL v2. See COPYING for details.
This documentation, which is part of the source code repository, is
provided under a Creative Commons Attribution-ShareAlike 3.0 Unported
License -- see http://creativecommons.org/licenses/by-sa/3.0/

197
README.mkd Normal file
View file

@ -0,0 +1,197 @@
<a name="start"></a>
# gitolite
Gitolite is an access control layer on top of git, which allows access control
down to the branch level, including specifying who can and cannot *rewind* a
given branch.
Gitolite comes with a **huge** amount of documentation. If you're absolutely
new, the suggested reading order is this:
* the README (this document) for a quick intro
* the [INSTALL][install] document
* the [ADMIN][admin] document
If you run into trouble start [here](#support). If you're migrating from
gitosis, read [this][migr].
And [here][who]'s some information on some of the projects and people using
gitolite (and who, in turn, have helped shape its features).
Once you've installed it and started using it, you'll want to explore some of
the more powerful features. All the documentation is available in the source
repo as well as [online][docs]. All the longer documents have tables of
contents, so you can quickly get a feel for what is covered right at the top.
----
In this document:
* <a href="#_what">what</a>
* <a href="#_why">why</a>
* <a href="#_main_features">main features</a>
* <a href="#_support">support</a>
* <a href="#_security">security</a>
* <a href="#_contact_and_license">contact and license</a>
----
<a name="_what"></a>
### what
Gitolite lets you use a single user on a server to host many git repositories
and provide access to many developers, without having to give them real
userids on or shell access to the server. The essential magic in doing this
is ssh's pubkey access and the `authorized_keys` file, and the inspiration was
an older program called gitosis.
Gitolite can restrict who can read from (clone/fetch) or write to (push) a
repository. It can also restrict who can push to what branch or tag, which is
very important in a corporate environment. Gitolite can be installed without
requiring root permissions, and with no additional software than git itself
and perl. It also has several other neat features described below and
elsewhere in the [doc/][docs] directory.
<a name="_why"></a>
### why
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 someone read-only
access to a repo; they either get read-write access or no access
* 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.
<a name="_main_features"></a>
### main features
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`).
Here're **some more features**. All of them, and more, are documented in
detail somewhere in gitolite's [doc/][docs] subdirectory.
* 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
<a name="support"></a>
<a name="_support"></a>
### support
Most installation problems are caused by not knowing ssh. Take a look at this
[transcript][] to see how simple it actually is, if your server's ssh daemon
is behaving itself.
If I suspect your problem is an ssh issue, I will probably ignore it. Please
learn how [gitolite uses ssh][doc9gas] and then methodically go through the
[ssh trouble shooting][doc6sts] document. These two documents contain
everything I could possibly tell you. I have nothing to add.
Even for other topics, please look through at least the table of contents of
at least the numbered documents to see if your question is already answered,
before asking.
<a name="_security"></a>
### 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.
For the first person to find a security hole in it, defined as allowing a
normal user (not the gitolite admin) to read a repo, or write/rewind a ref,
that the config file says he shouldn't, and caused by a bug in *code* that is
in the "master" branch, (not in the other branches, or the configuration file
or in Unix, perl, shell, etc.)... well I can't afford 1000 USD rewards like
djb, so you'll have to settle for 5000 INR (Indian Rupees) as a "token" prize
:-)
However, there are a few optional features (which must be explicitly enabled
in the RC file) where I just haven't had the time to reason about security
thoroughly enough. Please read the comments in `conf/example.gitolite.rc` for
details, looking for the word "security".
----
<a name="_contact_and_license"></a>
### 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
[transcript]: http://github.com/sitaramc/gitolite/blob/pu/doc/install-transcript.mkd
[install]: http://github.com/sitaramc/gitolite/blob/pu/doc/1-INSTALL.mkd
[admin]: http://github.com/sitaramc/gitolite/blob/pu/doc/2-admin.mkd
[migr]: http://github.com/sitaramc/gitolite/blob/pu/doc/migrate.mkd
[docs]: http://github.com/sitaramc/gitolite/blob/pu/doc
[doc9gas]: http://github.com/sitaramc/gitolite/blob/pu/doc/gitolite-and-ssh.mkd
[doc6sts]: http://github.com/sitaramc/gitolite/blob/pu/doc/ssh-troubleshooting.mkd
[who]: http://github.com/sitaramc/gitolite/blob/pu/doc/who-uses-it.mkd

View file

@ -1,99 +0,0 @@
#!/usr/bin/perl
use Cwd;
my $h = $ENV{HOME};
my $rc = "$h/.gitolite.rc";
my %count;
intro();
msg( FATAL => "no rc file found; do you even *have* g2 running?" ) if not -f $rc;
do $rc;
unless ( $return = do $rc ) {
msg( FATAL => "couldn't parse $rc: $@" ) if $@;
msg( FATAL => "couldn't do $rc: $!" ) unless defined $return;
msg( WARNING => "couldn't run $rc" ) unless $return;
}
print "checking rc file...\n";
rc_basic();
rest_of_rc();
print "\n";
print "checking conf file(s)...\n";
conf();
print "\n";
print "checking repos...\n";
repo();
print "\n";
print "...all done...\n";
# ----------------------------------------------------------------------
sub intro {
msg( INFO => "This program only checks for uses that make the new g3 completely unusable" );
msg( '' => "or that might end up giving *more* access to someone if migrated as-is." );
msg( '' => "It does NOT attempt to catch all the differences described in the docs." );
msg( '', '' );
msg( INFO => "'see docs' usually means the pre-migration checklist in" );
msg( '', => "'g2migr.html'; to get there, start from the main migration" );
msg( '', => "page at http://sitaramc.github.com/gitolite/install.html#migr" );
msg( '', '' );
}
sub rc_basic {
msg( FATAL => "GL_ADMINDIR in the wrong place -- aborting; see docs" ) if $GL_ADMINDIR ne "$h/.gitolite";
msg( NOTE => "GL_ADMINDIR is in the right place; assuming you did not mess with" );
msg( '', "GL_CONF, GL_LOGT, GL_KEYDIR, and GL_CONF_COMPILED" );
msg( FATAL => "REPO_BASE in the wrong place -- aborting; see docs" ) if $REPO_BASE ne "$h/repositories" and $REPO_BASE ne "repositories";
# ( abs or rel both ok)
}
sub rest_of_rc {
msg( SEVERE => "GIT_PATH found; see docs" ) if $GIT_PATH;
msg( SEVERE => "GL_ALL_INCLUDES_SPECIAL found; see docs" ) if $GL_ALL_INCLUDES_SPECIAL;
msg( SEVERE => "GL_NO_CREATE_REPOS not yet implemented" ) if $GL_NO_CREATE_REPOS;
msg( SEVERE => "rsync not yet implemented" ) if $RSYNC_BASE;
msg( WARNING => "ADMIN_POST_UPDATE_CHAINS_TO found; see docs" ) if $ADMIN_POST_UPDATE_CHAINS_TO;
msg( WARNING => "GL_NO_DAEMON_NO_GITWEB found; see docs" ) if $GL_NO_DAEMON_NO_GITWEB;
msg( WARNING => "GL_NO_SETUP_AUTHKEYS found; see docs" ) if $GL_NO_SETUP_AUTHKEYS;
msg( WARNING => "UPDATE_CHAINS_TO found; see docs" ) if $UPDATE_CHAINS_TO;
msg( WARNING => "GL_ADC_PATH found; see docs" ) if $GL_ADC_PATH;
msg( WARNING => "non-default GL_WILDREPOS_PERM_CATS found" ) if $GL_WILDREPOS_PERM_CATS ne 'READERS WRITERS';
}
sub conf {
chdir($h);
chdir($GL_ADMINDIR);
my $conf = `find . -name "*.conf" | xargs cat`;
msg( "SEVERE", "NAME rules; see docs" ) if $conf =~ m(NAME/);
msg( "SEVERE", "subconf command in admin repo; see docs" ) if $conf =~ m(NAME/conf/fragments);
msg( "SEVERE", "mirroring used; see docs" ) if $conf =~ m(config +gitolite\.mirror\.);
}
sub repo {
chdir($h);
chdir($REPO_BASE);
my @creater = `find . -name gl-creater`;
if (@creater) {
msg( WARNING => "found " . scalar(@creater) . " gl-creater files; see docs" );
}
my @perms = `find . -name gl-perms | xargs egrep -l -w R\\|RW`;
if (@perms) {
msg( WARNING => "found " . scalar(@perms) . " gl-perms files with R or RW; see docs" );
}
}
sub msg {
my ( $type, $text ) = @_;
print "$type" if $type;
print "\t$text\n";
exit 1 if $type eq 'FATAL';
$count{$type}++ if $type;
}

300
conf/example.conf Normal file
View file

@ -0,0 +1,300 @@
# example conf file for gitolite
# ----------------------------------------------------------------------------
# overall syntax:
# - everything is space-separated; no commas, semicolons, etc (except in
# the description string for gitweb)
# - comments in the normal shell-ish style; no surprises there
# - there are NO continuation lines of any kind
# - user/repo names as simple as possible; they must start with an
# alphanumeric, but after that they can also contain ".", "_", "-".
# - 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)
# objectives, over and above gitosis:
# - simpler syntax
# - easier gitweb/daemon control
# - specify who can push a branch/tag
# - specify who can rewind a branch/rewrite a tag
# ----------------------------------------------------------------------------
# GROUPS
# ------
# syntax:
# @groupname = [one or more names]
# groups let you club (user or group) names together for convenience
# * a group is like a #define in C except that it can *accumulate* values
# * the config file is parsed in a single-pass, so later *additions* to a
# group name cannot affect earlier *uses* of it
# The following examples should illustrate all this:
# you can have a group of people...
@staff = sitaram some_dev another-dev
# ...or a group of repos
@oss_repos = gitolite linux git perl rakudo entrans vkc
# ...or even a group of refexes
@important = master$ QA_done refs/tags/v[0-9]
# (see later for what "refex"s are; I'm only mentioning it
# here to emphasise that you can group them too)
# even sliced and diced differently
@admins = sitaram admin2
# notice that sitaram is in 2 groups (staff and admins)
# if you repeat a group name in another definition line, the
# new ones get added to the old ones (they accumulate)
@staff = au.thor
# so now "@staff" expands to all 4 names
# groups can include other groups, and the included group will
# be expanded to whatever value it currently has
@interns = indy james
@staff = bob @interns
# "@staff" expands to 7 names now
@interns = han
# "@interns" now has 3 names in it, but note that this does
# not change @staff
# REPO AND BRANCH PERMISSIONS
# ---------------------------
# syntax:
# start line:
# repo [one or more repos and/or repo groups]
# followed by one or more permissions lines:
# (C|R|RW|RW+|RWC|RW+C|RWD|RW+D|RWCD|RW+CD) [zero or more refexes] = [one or more users]
# there are 6 types of permissions: R, RW, and RW+ are simple (the "+" means
# permission to "rewind" -- force push a non-fast forward to -- a branch).
# The *standalone* C permission pertains to creating a REPO and is described
# in doc/4-wildcard-repositories.mkd. The C and D *suffixes* to the RW/RW+
# permissions pertain to creating or deleting a BRANCH, and are described in
# doc/3-faq-tips-etc.mkd, in the sections on "separating push and create
# rights" and "separating delete and rewind rights" respectively.
# how permissions are matched:
# - user, repo, and access (W or +) are known. For that combination, if
# any of the refexes match the refname being updated, the push succeeds.
# If none of them match, it fails
# what's a refex? a regex to match against the ref being updated (get it?)
# See next section for more on refexes
# BASIC PERMISSIONS (repo level only; apply to all branches/tags in repo)
# most important rule of all -- specify who can make changes
# to *this* file take effect
repo gitolite-admin
RW+ = @admins
# "@all" is a special, predefined, group name of all users
# (everyone who has a pubkey in keydir)
repo testing
RW+ = @all
# this repo is visible to staff but only sitaram can write to it
repo gitolite
R = @staff
RW+ = sitaram
# you can split up access rules for a repo for convenience
# (notice that @oss_repos contains gitolite also)
repo @oss_repos
R = @all
# set permissions to all repos. *Please* do see
# doc/3-faq-tips-etc.mkd for notes on this feature
repo @all
RW+ = @admins
# SPECIFYING AND USING A REFEX
# - refexes are specified in perl regex syntax
# - refexes are prefix-matched (they are internally anchored with "^"
# before being used), which means a refex like "refs/tags/v[0-9]"
# matches anything *starting with* that pattern. There may be text
# after it (example: refs/tags/v4-r3/p7), and it will still match
# ADVANCED PERMISSIONS USING REFEXES
# - if no refex appears, the rule applies to all refs in that repo
# - a refex is automatically prefixed by "refs/heads/" if it doesn't start
# with "refs/" (so tags have to be explicitly named as
# refs/tags/pattern)
# here's the example 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
# and here're the equivalent gitolite refexes
repo git
RW master = junio
RW+ pu = junio
RW cogito$ = pasky
RW bw/ = linus
RW tmp/ = @all
RW refs/tags/v[0-9] = junio
# DENY/EXCLUDE RULES
# ***IMPORTANT NOTES ABOUT "DENY" RULES***:
# - deny rules do NOT affect read access. They only apply to `W` and `+`.
#
# - when using deny rules, the order of your rules starts to matter, where
# earlier it did not. The first matching rule applies, where "matching" is
# defined as either permitting the operation you're attempting (`W` or `+`),
# which results in success, or a "deny" (`-`), which results in failure.
# (As before, a fallthrough also results in failure).
#
# - do not use `@all` when your config has any deny rules; it won't work as
# you probably expect it to!
# in the example above, you cannot easily say "anyone can write any tag,
# except version tags can only be written by junio". The following might look
# like it works but it doesn't:
# RW refs/tags/v[0-9] = junio
# RW refs/tags/ = junio linus pasky @others
# if you use "deny" rules, however, you can do this (a "deny" rule just uses
# "-" instead of "R" or "RW" or "RW+" in the permission field)
RW refs/tags/v[0-9] = junio
- refs/tags/v[0-9] = linus pasky @others
RW refs/tags/ = junio linus pasky @others
# FILE/DIR NAME BASED RESTRICTIONS
# --------------------------------
# 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/".
# GITWEB AND DAEMON STUFF
# -----------------------
# No specific syntax for gitweb and daemon access; just make the repo readable
# ("R" access) to the special users "gitweb" and "daemon"
# make "@oss_repos" (all 7 of them!) accessible via git daemon
repo @oss_repos
R = daemon
# make the two *large* repos accessible via gitweb
repo linux perl
R = gitweb
# REPO OWNER/DESCRIPTION LINE FOR GITWEB
# syntax, one of:
# reponame = "some description string in double quotes"
# reponame "owner name" = "some description string in double quotes"
# note: setting a description also gives gitweb access; you do not have to
# give gitweb access as described above if you're specifying a description
gitolite "Sitaram Chamarty" = "fast, secure, access control for git in a corporate environment"
# REPO SPECIFIC GITCONFIG
# -----------------------
# update 2010-02-06; this won't work unless the rc file has the right
# settings; please see comments around the variable $GL_GITCONFIG_KEYS in
# conf/example.gitolite.rc for details and security information.
# (Thanks to teemu dot matilainen at iki dot fi)
# this should be specified within a "repo" stanza
# syntax:
# config sectionname.keyname = [optional value_string]
# example usage: if you placed a hook in hooks/common that requires
# configuration information that is specific to each repo, you could do this:
repo gitolite
config hooks.mailinglist = gitolite-commits@example.tld
config hooks.emailprefix = "[gitolite] "
config foo.bar = ""
config foo.baz =
# 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 (--add, the value_regex, etc) are not supported.
# INCLUDE SOME OTHER FILE
# -----------------------
include "foo.conf"
# this includes the contents of $GL_ADMINDIR/conf/foo.conf here
# Notes:
# - the include statement is not allowed inside delegated fragments for
# security reasons.
# - you can also use an absolute path if you like, although in the interests
# of cloning the admin-repo sanely you should avoid doing this!
# EXTERNAL COMMAND HELPERS -- RSYNC
# ---------------------------------
# If $RSYNC_BASE is non-empty, the following config entries come into play
# (otherwise they are ignored):
# a "fake" git repository to collect rsync rules. Gitolite does not
# auto-create any repo whose name starts with EXTCMD/
repo EXTCMD/rsync
# grant 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" above
RW NAME/ = sitaram
RW NAME/foo/ = user1
R NAME/bar/ = user2
# just to remind you that these are perl regexes, not shell globs
RW NAME/baz/.*/*.c = user3

295
conf/example.gitolite.rc Normal file
View file

@ -0,0 +1,295 @@
# paths and configuration variables for gitolite
# please read comments before editing
# this file is meant to be pulled into a perl program using "do" or "require".
# You do NOT need to know perl to edit the paths; it should be fairly
# self-explanatory and easy to maintain perl syntax :-)
# --------------------------------------
# Do not uncomment these values unless you know what you're doing
# $GL_PACKAGE_CONF = "";
# $GL_PACKAGE_HOOKS = "";
# --------------------------------------
# MIRRORING SUPPORT
# $GL_SLAVE_MODE = 0;
# $ENV{GL_SLAVES} = 'gitolite@server2 gitolite@server3';
# PLEASE USE SINGLE QUOTES ABOVE, NOT DOUBLE QUOTES
# see doc/mirroring.mkd for details
# --------------------------------------
# this is where the repos go. If you provide a relative path (not starting
# with "/"), it's relative to your $HOME. You may want to put in something
# like "/bigdisk" or whatever if your $HOME is too small for the repos, for
# example
$REPO_BASE="repositories";
# the default umask for repositories is 0077; change this if you run stuff
# like gitweb and find it can't read the repos. Please note the syntax; the
# leading 0 is required
$REPO_UMASK = 0077; # gets you 'rwx------'
# $REPO_UMASK = 0027; # gets you 'rwxr-x---'
# $REPO_UMASK = 0022; # gets you 'rwxr-xr-x'
# part of the setup of gitweb is a variable called $projects_list (please see
# gitweb documentation for more on this). Set this to the same value:
$PROJECTS_LIST = $ENV{HOME} . "/projects.list";
# 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_ALL_INCLUDES_SPECIAL = 0;
# --------------------------------------
# I see no reason anyone may want to change the gitolite admin directory, but
# feel free to do so. However, please note that it *must* be an *absolute*
# path (i.e., starting with a "/" character)
# gitolite admin directory, files, etc
$GL_ADMINDIR=$ENV{HOME} . "/.gitolite";
# --------------------------------------
# templates for location of the log files and format of their names
# I prefer this template (note the %y and %m placeholders)
# it produces files like `~/.gitolite/logs/gitolite-2009-09.log`
$GL_LOGT="$GL_ADMINDIR/logs/gitolite-%y-%m.log";
# other choices are below, or you can make your own -- but PLEASE MAKE SURE
# the directory exists and is writable; gitolite won't do that for you (unless
# it is the default, which is "$GL_ADMINDIR/logs")
# $GL_LOGT="$GL_ADMINDIR/logs/gitolite-%y-%m-%d.log";
# $GL_LOGT="$GL_ADMINDIR/logs/gitolite-%y.log";
# --------------------------------------
# location of the performance log files
# uncomment and set this variable if you want performance logging
#
# perf log files are different from access log files; they store different
# information, are not meant to be as long-lived, and so on
# $GL_PERFLOGT="$GL_ADMINDIR/logs/perf-gitolite-%y-%m.log";
# --------------------------------------
# Please DO NOT change these three paths
$GL_CONF="$GL_ADMINDIR/conf/gitolite.conf";
$GL_KEYDIR="$GL_ADMINDIR/keydir";
$GL_CONF_COMPILED="$GL_ADMINDIR/conf/gitolite.conf-compiled.pm";
# --------------------------------------
# if git on your server is on a standard path (that is
# ssh git@server git --version
# works), leave this setting as is. Otherwise, choose one of the
# alternatives, or write your own
$GIT_PATH="";
# $GIT_PATH="/opt/bin/";
# --------------------------------------
# ----------------------------------------------------------------------
# BIG CONFIG SETTINGS
# Please read doc/big-config.mkd for details
$GL_BIG_CONFIG = 0;
$GL_NO_DAEMON_NO_GITWEB = 0;
$GL_NO_CREATE_REPOS = 0;
$GL_NO_SETUP_AUTHKEYS = 0;
# ----------------------------------------------------------------------
# SECURITY SENSITIVE SETTINGS
#
# Settings below this point may have security implications. That
# usually means that I have not thought hard enough about all the
# possible ways to crack security if these settings are enabled.
# Please see details on each setting for specifics, if any.
# ----------------------------------------------------------------------
# --------------------------------------
# ALLOW REPO ADMIN TO SET GITCONFIG KEYS
#
# Gitolite allows you to set git repo options using the "config" keyword; see
# conf/example.conf 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 may 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 from
# the repo config).
$GL_GITCONFIG_KEYS = "";
# The second choice is to give it a space separated list of settings you
# consider safe. (These are actually treated as a set of 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"
# category:
# $GL_GITCONFIG_KEYS = "foo\\..*";
# --------------------------------------
# ALLOW GITCONFIG KEYS EVEN FOR WILD REPOS
#
# This is an efficiency issue more than a security issue, since this requires
# trawling through all of $REPO_BASE looking for stuff :)
# $GL_GITCONFIG_WILD = 0;
# --------------------------------------
# EXTERNAL COMMAND HELPER -- HTPASSWD
# security note: runs an external command (htpasswd) with specific arguments,
# including a user-chosen "password".
# if you want to enable the "htpasswd" command, give this the absolute path to
# whatever file apache (etc) expect to find the passwords in.
$HTPASSWD_FILE = "";
# Look in doc/3 ("easier to link gitweb authorisation with gitolite" section)
# for more details on using this feature.
# --------------------------------------
# EXTERNAL COMMAND HELPER -- RSYNC
# security note: runs an external command (rsync) with specific arguments, all
# presumably filled in correctly by the client-side rsync.
# base path of all the files that are accessible via rsync. Must be an
# absolute path. Leave it undefined or set to the empty string to disable the
# rsync helper.
$RSYNC_BASE = "";
# $RSYNC_BASE = "/home/git/up-down";
# $RSYNC_BASE = "/tmp/up-down";
# --------------------------------------
# EXTERNAL COMMAND HELPER -- SVNSERVE
# security note: runs an external command (svnserve) with specific arguments,
# as specified below. %u is substituted with the username.
# This setting allows launching svnserve when requested by the ssh client.
# This allows using the same SSH setup (hostname/username/public key) for both
# SVN and git access. Leave it undefined or set to the empty string to disable
# svnserve access.
$SVNSERVE = "";
# $SVNSERVE = "/usr/bin/svnserve -r /var/svn/ -t --tunnel-user=%u";
# --------------------------------------
# ALLOW REPO CONFIG TO USE WILDCARDS
# security note: this used to in a separate "wildrepos" branch. You can
# create repositories based on wild cards, give "ownership" to the specific
# user who created it, allow him/her to hand out R and RW permissions to other
# users to collaborate, etc. This is powerful stuff, and I've made it as
# secure as I can, but it hasn't had the kind of rigorous line-by-line
# analysis that the old "master" branch had.
# This has now been rolled into master, with all the functionality gated by
# this variable. Set this to 1 if you want to enable the wildrepos features.
# Please see doc/4-wildcard-repositories.mkd for details.
$GL_WILDREPOS = 0;
# --------------------------------------
# DEFAULT WILDCARD PERMISSIONS
# 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. Note that @all can be
# used here but is special; no other groups can be used in user-level permissions.
# $GL_WILDREPOS_DEFPERMS = 'R @all';
# --------------------------------------
# HOOK CHAINING
# 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
# $UPDATE_CHAINS_TO = "hooks/update.secondary";
# $ADMIN_POST_UPDATE_CHAINS_TO = "hooks/post-update.secondary";
# --------------------------------------
# ADMIN DEFINED COMMANDS
# WARNING: Use this feature only if (a) you really really know what you're
# doing or (b) you really don't care too much about security. Please read
# doc/admin-defined-commands.mkd for details.
# $GL_ADC_PATH = "";
# --------------------------------------
# SITE-SPECIFIC INFORMATION
# Some installations would like to give their users customised information
# (like a link to their own websites, for example) so that each end user does
# not have to grok all the gitolite documentation.
# If this variable is defined, the "info" command will print it at the end of
# the listing.
# $GL_SITE_INFO = "";
# $GL_SITE_INFO = "XYZ.COM DEVELOPERS: PLEASE SEE http://xyz.com/gitolite/help first";
# --------------------------------------
# USERGROUP HANDLING
# 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.
# $GL_GET_MEMBERSHIPS_PGM = "/usr/local/bin/expand-ldap-user-to-groups"
# --------------------------------------
# per perl rules, this should be the last line in such a file:
1;
# Local variables:
# mode: perl
# End:
# vim: set syn=perl:

46
contrib/adc/able Executable file
View file

@ -0,0 +1,46 @@
#!/bin/bash
# WARNING: USES BASH FEATURES TO AVOID A TEMP FILE; CAN BE FIXED IF NEEDED
. $(dirname $0)/adc.common-functions
get_rights_and_owner gitolite-admin
[ -z "$perm_write" ] && 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"
;;
* )
[ -d $loc ] && locs="$locs $GL_REPO_BASE_ABS/$1.git"
[ -d $loc ] || echo "ignoring $1..."
;;
esac
shift
done
case $op in
en|enable )
for l in $locs
do
rm -fv $l/.gitolite.down
done
;;
dis|disable )
# bashism
read msg <<<$(cat)
for l in $locs
do
echo $msg > $l/.gitolite.down
done
;;
* )
die "argument 1 must be 'en' or 'dis'"
;;
esac

View file

@ -0,0 +1,17 @@
#!/bin/sh
# please make sure this file is NOT chmod +x
die() { echo "$@"; exit 1; }
get_rights_and_owner() {
local ans
ans=$(perl -I$GL_BINDIR -Mgitolite -e 'cli_repo_rights("'$1'")')
# 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=
}

32
contrib/adc/fork Executable file
View file

@ -0,0 +1,32 @@
#!/bin/sh
from=$1
to=$2
. $(dirname $0)/adc.common-functions
get_rights_and_owner $from
[ -z "$perm_read" ] && die "no read permissions on $from"
get_rights_and_owner $to
[ -z "$perm_create" ] && die "no create permissions on $to"
# clone $from to $to
git clone --bare -l $GL_REPO_BASE_ABS/$from.git $GL_REPO_BASE_ABS/$to.git
[ $? -ne 0 ] && exit 1
# 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"
( cd $HOME;perl -le 'do ".gitolite.rc"; print $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
# 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

88
contrib/adc/gl-reflog Executable file
View file

@ -0,0 +1,88 @@
#!/usr/bin/perl -w
use strict;
use warnings;
# - 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
# 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 "+"
# 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;
require "$ENV{GL_BINDIR}/gitolite.pm" or die "parse gitolite.pm failed\n";
my ($perm, $creator, $wild) = &repo_rights($repo);
die "you don't have read access to $repo\n" unless $perm =~ /R/;
my @logfiles = sort glob("$ENV{GL_ADMINDIR}/logs/*");
# 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");
}

11
contrib/adc/restrict-admin Executable file
View file

@ -0,0 +1,11 @@
#!/bin/sh
. $(dirname $0)/adc.common-functions
get_rights_and_owner gitolite-admin
[ -z "$perm_write" ] && 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!

17
contrib/adc/rmrepo Executable file
View file

@ -0,0 +1,17 @@
#!/bin/sh
. $(dirname $0)/adc.common-functions
delete=$1
get_rights_and_owner $delete
[ "$owner" = "$GL_USER" ] || die "$delete is not yours to delete!"
cd $GL_REPO_BASE_ABS
rm -rf $delete.git
cd $HOME
PROJECTS_LIST=$(perl -e 'do ".gitolite.rc"; print $PROJECTS_LIST')
export delete
perl -ni -e 'print unless /^\Q$ENV{delete}.git\E$/' $PROJECTS_LIST

56
contrib/adc/su-expand Executable file
View file

@ -0,0 +1,56 @@
#!/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
get_rights_and_owner gitolite-admin
[ -z "$perm_write" ] && 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

24
contrib/adc/sudo Executable file
View file

@ -0,0 +1,24 @@
#!/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
get_rights_and_owner gitolite-admin
[ -z "$perm_write" ] && 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 "$@"

35
contrib/autotoc Executable file
View file

@ -0,0 +1,35 @@
#!/usr/bin/perl -w
use strict;
# filter gitolite's mkd files through this; it's designed to be idempotent of
# course
sub make_anchor {
# make an anchor out of a section heading
my $sh = shift;
$sh =~ s/\W+/_/g;
$sh =~ s/^_//;
return $sh;
}
undef $/;
my $doc = <>;
$doc =~ s/^<a name="_.*"><\/a>\n\n//mg;
$doc =~ s/^<a name="AUTO_.*"><\/a>\n\n//mg;
my @toc = $doc =~ /^###+ .*/mg;
$doc =~ s/^(###+) (.*)/"<a name=\"_" . &make_anchor($2) . "\"><\/a>\n\n$1 $2"/mge;
for (@toc) {
s/^(###+) (.*)/' ' x (length($1)-3) . ' * <a href="#_' . &make_anchor($2) . "\">$2<\/a>"/e;
}
my $toc = "In this document:\n\n";
$toc .= join("\n", @toc);
$toc .= "\n\n";
$doc =~ s/^In this document:\n\n.*?\n\n/$toc/sm;
print $doc;

113
contrib/gerrit.mkd Normal file
View file

@ -0,0 +1,113 @@
# 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!]
[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 :)
* **Reference-level access controls** make no mention of regexes. I'm not
[JWZ][jwzq], and I strongly consider regexes a plus point :)
* They also make no mention of giving permissions to individual users, only
groups. If this is true, it would be a little cumbersome when managing
many small projects -- projects where you have only one QA, one
integrator, etc., would end up making many 1-man groups. [If anyone who's
used gerrit can correct me on this I'd appreciate it].
* **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.
* One big difference is that gitolite does not process "deny" rules ("-1 no
Access" in gerrit terms) for *read* access -- we only support those for
write access. Gerrit uses this to "hide a handful of projects on an
otherwise public server"; in gitolite you'd better avoid giving `R = @all`
in the first place :)
* [Update 2010-04-14: it appears that Gerrit is also in the process of
implementing *read* access control at the branch level -- they can afford
to even think of that because they have a full jgit stack to play with.
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) ;-)]
**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
* gitolite always allows creating a branch. The only way to prevent that is
to list out allowed branches explicitly (make sure you end the refex with
a `$`!).
* Force push is the same as delete: historically (and by default, even now)
gitolite does the same . However, I've only recently (and somewhat
reluctantly) changed gitolite to allow treating these two separately.
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).

View file

@ -0,0 +1,15 @@
# 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)

View file

@ -0,0 +1,40 @@
# --------------------------------------------
# Per-repo authorization based on gitolite ACL
# Include this in gitweb.conf
# See doc/3-faq-tips-etc.mkd for more info
# HOME of the gitolite user
my $gl_home = "/home/git";
# environment variables needed by gitolite.pm
$ENV{GL_RC} = "$gl_home/.gitolite.rc";
$ENV{GL_USER} = $cgi->remote_user || "gitweb";
# variables from the RC file
our ($REPO_BASE, $GL_ADMINDIR);
# set HOME temporarily for RC parsing
my $orig_home = $ENV{HOME};
$ENV{HOME} = $gl_home;
do $ENV{GL_RC}
or die_error(500, "Failed to parse $ENV{GL_RC}: " . ($! or $@));
$ENV{HOME} = $orig_home;
# set project root etc. absolute paths
$ENV{GL_REPO_BASE_ABS} = ( $REPO_BASE =~ m(^/) ? $REPO_BASE : "$gl_home/$REPO_BASE" );
$projects_list = $projectroot = $ENV{GL_REPO_BASE_ABS};
# load gitolite helper routines
require "$GL_ADMINDIR/src/gitolite.pm"
or die_error(500, "Failed to parse gitolite.pm: " . ($! or $@));
$export_auth_hook = sub {
my $repo = shift;
# gitweb passes us the full repo path; so we strip the beginning
# 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/);
};

11
contrib/vim/README.mkd Normal file
View file

@ -0,0 +1,11 @@
# 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

View file

@ -1,127 +0,0 @@
#!/usr/bin/perl -w
#
# migrate gitosis.conf to gitolite.conf format
#
# Based on gl-conf-convert by: Sitaram Chamarty
# Rewritten by: Behan Webster <behanw@websterwood.com>
#
use strict;
use warnings;
if (not @ARGV and -t or @ARGV and $ARGV[0] eq '-h') {
print "Usage:\n gl-conf-convert < gitosis.conf > gitolite.conf\n(please see the documentation for details)\n";
exit 1;
}
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 (/^\[gitosis\]$/) {
$groupname = '';
$reponame = '@all';
} 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";
#}
}

345
doc/1-INSTALL.mkd Normal file
View file

@ -0,0 +1,345 @@
# gitolite installatation
In this document:
* <a href="#_please_read_this_first">please read this first</a>
* <a href="#_important_notes">important notes</a>
* <a href="#_conventions_used">conventions used</a>
* <a href="#_requirements">requirements</a>
* <a href="#_client_workstation">client/workstation</a>
* <a href="#_server">server</a>
* <a href="#_technical_skills">technical skills</a>
* <a href="#_installation_and_setup">installation and setup</a>
* <a href="#_install_methods_and_deciding_which_one_to_use">install methods and deciding which one to use</a>
* <a href="#_package_method_directly_on_the_server_using_RPM_DEB">(package method) directly on the server, using RPM/DEB</a>
* <a href="#_root_method_directly_on_the_server_manually_with_root_access">(root method) directly on the server, manually, with root access</a>
* <a href="#_non_root_method_directly_on_the_server_manually_without_root_access">(non-root method) directly on the server, manually, without root access</a>
* <a href="#_from_client_method_install_from_the_client_to_the_server">(from-client method) install from the client to the server</a>
* <a href="#_URLs_for_gitolite_managed_repos">URLs for gitolite-managed repos</a>
* <a href="#_special_cases_multiple_gitolite_servers">special cases -- multiple gitolite servers</a>
* <a href="#_package_method_and_root_method">package method and root method</a>
* <a href="#_from_client_method">from-client method</a>
* <a href="#_upgrading">upgrading</a>
* <a href="#_uninstalling">uninstalling</a>
* <a href="#_cleaning_out_a_botched_install">cleaning out a botched install</a>
* <a href="#_uninstalling_gitolite_completely">uninstalling gitolite completely</a>
----
<a name="_please_read_this_first"></a>
### please read this first
<a name="_important_notes"></a>
#### important notes
Please make sure you understand the following points first.
* gitolite runs as a single user on a server, and is invoked via ssh. Thus,
every user on the server is a potential "gitolite host".
* 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 [doc/ssh-troubleshooting.mkd][doc6] for help.
A gitolite setup has:
* a server
* a "hosting user" on the server -- the userid under which gitolite runs.
You can have any number of "hosting users" on one server; in fact every
user can host their own gitolite instance
* an "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
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.
<a name="_conventions_used"></a>
#### conventions used
Throughout the documentation, we use "sitaram" 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**.
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.
<a name="_requirements"></a>
#### requirements
<a name="_client_workstation"></a>
##### client/workstation
* git version 1.6.2 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
* if you're using the "from-client" method of install (see below), the bash
shell is needed
* again, msysgit on Windows is fine
<a name="_server"></a>
##### 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.2 or greater
* 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 (but since git requires it anyway, you probably have it)
* openssh or any ssh that can understand the `authorized_keys` file format
<a name="_technical_skills"></a>
##### 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.
<a name="_installation_and_setup"></a>
### installation and setup
<a name="methods"></a>
<a name="_install_methods_and_deciding_which_one_to_use"></a>
#### install methods and deciding which one to use
Gitolite has 4 install methods:
* **package method** if you have a gitolite RPM or a DEB available
* **root method** if you have root access to the server, and you plan to
have multiple "hosting users" on it
* **non-root method** if you don't have root access to the server, but you
do have at least one account with a password
* **from-client method** if you are not comfortable with public keys and
server side commands
Here's how you install using these 3 methods. Future upgrades are equally
easy -- the steps required for upgrading are marked "(U)".
<a name="_package_method_directly_on_the_server_using_RPM_DEB"></a>
#### (package method) directly on the server, using RPM/DEB
* from your workstation, copy your `~/.ssh/id_rsa.pub` file to the server.
Put it in `/tmp/sitaram.pub`.
* (U) on the server, as root, do the install (urpmi, yum, apt-get, etc.).
* on the server, "su - git", then as "git" user, run `gl-setup
/tmp/sitaram.pub`.
* on the client, run `cd; git clone git@server:gitolite-admin`
<a name="_root_method_directly_on_the_server_manually_with_root_access"></a>
#### (root method) directly on the server, manually, with root access
* from your workstation, copy your `~/.ssh/id_rsa.pub` file to the server.
Put it in `/tmp/sitaram.pub`.
* (U) on the server, as root, do the following:
cd $HOME
git clone git://github.com/sitaramc/gitolite gitolite-source
cd gitolite-source
# now checkout whatever branch you want; for early adopters I suggest
# "pu", as in "git checkout -t origin/pu" for recent gits
mkdir -p /usr/local/share/gitolite/conf /usr/local/share/gitolite/hooks
src/gl-system-install /usr/local/bin /usr/local/share/gitolite/conf /usr/local/share/gitolite/hooks
* on the server, "su - git", then as "git" user, run `gl-setup
/tmp/sitaram.pub`.
* on the client, run `cd; git clone git@server:gitolite-admin`
<a name="_non_root_method_directly_on_the_server_manually_without_root_access"></a>
#### (non-root method) directly on the server, manually, without root access
WARNING: if you use this method you'd better know enough about ssh to be able
to keep your keys straight, and you'd also better have password access to the
server so that if you screw up the keys you can still get on, or be able to
"su - git" from some other user on the server.
* from your workstation, copy your `~/.ssh/id_rsa.pub` file to the server.
Put it in `/tmp/sitaram.pub`.
* if `$HOME/bin` is not on the default PATH, fiddle with your `.bashrc` or
`.bash_profile` or similar files and add it somehow.
* (U) on the server, as "git", do the following:
cd $HOME
git clone git://github.com/sitaramc/gitolite gitolite-source
# now checkout whatever branch you want; for early adopters I suggest
# "pu", as in "git checkout -t origin/pu" for recent gits
cd gitolite-source
mkdir -p $HOME/bin $HOME/share/gitolite/conf $HOME/share/gitolite/hooks
src/gl-system-install $HOME/bin $HOME/share/gitolite/conf $HOME/share/gitolite/hooks
* on the server, still as "git", run `gl-setup /tmp/sitaram.pub`.
* on the client, run `cd; git clone git@server:gitolite-admin`
<a name="fc"></a>
<a name="_from_client_method_install_from_the_client_to_the_server"></a>
#### (from-client method) install from the client to the server
The advantage of this method is that it forces you to solve the ssh pubkey
problem **before** attempting to install. It works best if you have dedicated
userids, one on the server for installing gitolite, and one the client for
administering it.
Sadly, it also forces the admin to use a different URL to access gitolite
repos than normal users, which seems to confuse a heck of a lot of people who
don't read the prominently displayed messages and/or the documentation.
This method is verbosely documented in this [transcript][], including
*outputs* of the commands concerned.
<a name="_URLs_for_gitolite_managed_repos"></a>
### URLs for gitolite-managed repos
The URL for normal users (i.e., users other than the admin) is always of the
form "git@server:reponame". So, for instance, `git clone git@server:testing`
gets any valid user a copy of the "testing" repo.
In the first 3 install methods, the admin user will also use the same URL
format, like `git clone git@server:gitolite-admin`.
However, in the fourth ("from-client") method, the admin user needs a
different URL (`gitolite:reponame`) to gain access to the gitolite
repositories. Check [here][twokeys] for why.
<a name="_special_cases_multiple_gitolite_servers"></a>
### special cases -- multiple gitolite servers
<a name="_package_method_and_root_method"></a>
#### package method and root method
With the first two methods of installation, it's trivial to create multiple
gitolite instances (say one for each department, on some mega company-wide
server). You can even do this without giving shell access to the admins.
Here's an example with just two "departments", and their admins Alice and Bob:
* create userids `webbrowser_repos` and `webserver_repos`
* ask Alice and Bob for their pubkeys; copy them to the respective home
directories for convenience
* run `su - webbrowser_repos`, then `gl-setup alice.pub`
* (similarly with `webserver_repos` and `bob.pub`, and so on for others)
That's it. The URL for all web browser projects is now something like
`webbrowser_repos@server:reponame`, and similarly for the others.
Notice that you only have to do this once for each "department", and it's
really just one command after creating the userid. None of these admins need
to have a command line on the server, so don't give them the passwords if you
don't need to -- the pubkey will allow them to be gitolite admins on their
domain, and that's quite enough for normal operations.
<a name="_from_client_method"></a>
#### from-client method
Thanks to Matt Perzel, the easy-install command now takes an optional 4th
parameter, which is the "nickname" of the gitolite server. It gets defined in
`~/.ssh/config`, and if not used it defaults to "gitolite".
So if you used the following command to install gitolite to 2 different
servers:
./src/gl-easy-install -q git my.1st.git.server admin_user1 gitolite_server_1
./src/gl-easy-install -q git my.2nd.git.server admin_user1 gitolite_server_2
you will find that `~/gitolite_server_1-admin` and `~/gitolite_server_2-admin`
have been created as respective clones. Or you can re-clone elsewhere:
cd ~/admin1; git clone gitolite_server_1:gitolite-admin.git
cd ~/admin2; git clone gitolite_server_2:gitolite-admin.git
<a name="_upgrading"></a>
### upgrading
Upgrading gitolite is easy. In each method above, just re-do the step that is
marked "(U)". Also, if you're using either of the two methods that use the
`src/gl-system-install` command, please make sure you give it the same
arguments!
If you've added any new hooks, please also run the next step (`gl-setup`)
also.
Also, remember that some new features may require additional settings in your
`~/.gitolite.rc` file.
<a name="_uninstalling"></a>
### uninstalling
<a name="_cleaning_out_a_botched_install"></a>
#### cleaning out a botched install
When people have trouble installing gitolite, they often try to change a bunch
of things manually on the server. This usually makes things worse ;-) so
here's how to clean the slate.
* client-side
* edit `~/.ssh/config` and delete the paragraph starting with `host
gitolite`, if present.
* remove `~/gitolite-admin`
* server-side
* edit `~/.ssh/authorized_keys` and delete all lines between `# gitolite
start` and `# gitolite end` inclusive.
* remove `~/.gitolite`, `~/.gitolite.rc` and
`~/repositories/gitolite-admin.git`
<a name="_uninstalling_gitolite_completely"></a>
#### uninstalling gitolite completely
There's some duplication between this and the previous section, but
uninstalling gitolite is described in great detail in
[doc/uninstall.mkd][doc9unin]
----
[doc6]: http://github.com/sitaramc/gitolite/blob/pu/doc/ssh-troubleshooting.mkd
[doc9unin]: http://github.com/sitaramc/gitolite/blob/pu/doc/uninstall.mkd
[twokeys]: http://github.com/sitaramc/gitolite/blob/pu/doc/ssh-troubleshooting.mkd#twokeys
[transcript]: http://github.com/sitaramc/gitolite/blob/pu/doc/install-transcript.mkd

257
doc/2-admin.mkd Normal file
View file

@ -0,0 +1,257 @@
# administering and running gitolite
In this document:
* <a href="#_please_read_this_first">please read this first</a>
* <a href="#_adding_users_and_repos">adding users and repos</a>
* <a href="#_using_hooks">using hooks</a>
* <a href="#_custom_hooks">custom hooks</a>
* <a href="#_gl_post_init_hook">"gl-post-init" hook</a>
* <a href="#_hook_chaining">hook chaining</a>
* <a href="#_environment_variables_available_to_hooks">environment variables available to hooks</a>
* <a href="#_other_features">other features</a>
* <a href="#_moving_pre_existing_repos_into_gitolite">moving pre-existing repos into gitolite</a>
* <a href="#_specifying_gitweb_and_daemon_access">specifying gitweb and daemon access</a>
* <a href="#_custom_git_config">custom git config</a>
----
<a name="_please_read_this_first"></a>
### please read this first
Unless you know what you're doing, do not do **anything** manually on the
server, like adding new repositories or users or changing the access control
rules. 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.
<a name="_adding_users_and_repos"></a>
### adding 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
`conf/example.conf` in the gitolite source 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.
<a name="_using_hooks"></a>
### using hooks
<a name="_custom_hooks"></a>
#### custom hooks
You can supply your own, custom, hook scripts if you wish. Install gitolite
as usual, then:
* if you installed using "from-client" method (gl-easy-install):
* go to the gitolite *source* clone from which you did the original
install
* add your new hook into "hooks/common"
* run src/gl-easy-install with the same arguments as you ran the first
time
* if you installed using one of the other methods
* 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.
**VERY IMPORTANT SECURITY NOTE: 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.**
<a name="_gl_post_init_hook"></a>
#### "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.
<a name="_hook_chaining"></a>
#### hook chaining
Gitolite basically takes over the update hook for all repos, but some setups
really need the update hook functionality for their own purposes too. In
order to allow this, Gitolite now exec's a hook called `update.secondary` when
it's own "update" hook is done and everything is ready to go.
You can create this `update.secondary` hook manually on selected repos on the
server, or use the mechanism in the previous section to make gitolite put it
on *all* your repos.
Similarly, gitolite also takes over the post-update hook for the special
"gitolite-admin" repo. This hook will also chain to a `post-update.secondary`
if such a hook exists. People wishing to do exotic things on the server side
when the admin repo is pushed should see doc/shell-games.notes for how to
exploit this :-)
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.
<a name="_environment_variables_available_to_hooks"></a>
#### 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
<a name="_other_features"></a>
### other features
<a name="_moving_pre_existing_repos_into_gitolite"></a>
#### moving pre-existing repos into gitolite
One simple way to add a pre-existing repo to gitolite is to let gitolite
create it as a brand new repo as in the previous section, then do the
following:
cd your-copy-of-the-repo
# make sure all the branches are correct and no extra stuff, "temp"
# branches, etc., are present
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).
All this is actually very simple and easily done. However, if you have many
existing repos to add, this can be time-consuming and error-prone. Here's how
to take a bunch of existing repos and add them to gitolite:
* make 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. Then add, commit,
push.
<a name="gwd"></a>
<a name="_specifying_gitweb_and_daemon_access"></a>
#### specifying gitweb and daemon access
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 defines two "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.
This gives you a quick way to offer multiple repos up for gitweb/daemon
access.
However, setting a description for the project also enables gitweb permissions
so you may as well use that method and kill two birds with one stone, like so:
gitolite = "fast, secure, access control for git in a corporate environment"
You can also specify an owner for gitweb to show, if you like:
gitolite "Sitaram Chamarty" = "fast, secure, access control for git in a corporate environment"
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**.
<a name="_custom_git_config"></a>
#### custom git config
The custom hooks feature is a blunt instrument -- all repos get the hook you
specified and will run it. In order to make it a little more fine-grained,
you could set your hooks to only work if a certain "gitconfig" variable was
set. Which means we now need a way to specify "git config" settings on a per
repository basis.
Thanks to Teemu (teemu dot matilainen at iki dot fi), gitolite now does this
very easily. For security reasons, this can only be done from the master
config file (i.e., if you're using delegation, the delegated admins cannot
specify git config settings).
Please see `conf/example.conf` for syntax. Note that this only supports the
basic forms of the "git config" command:
git config section.key value # value may be an empty string
git config --unset-all section.key
It does not (currently) support other options like `--add`, the `value_regex`,
etc.
[genpub]: http://sitaramc.github.com/0-installing/2-access-gitolite.html#generating_a_public_key

724
doc/3-faq-tips-etc.mkd Normal file
View file

@ -0,0 +1,724 @@
# assorted faqs, tips, and notes on gitolite
In this document:
* <a href="#_common_errors_and_mistakes">common errors and mistakes</a>
* <a href="#_git_version_dependency">git version dependency</a>
* <a href="#_other_errors_warnings_notes_">other errors, warnings, notes...</a>
* <a href="#_cloning_an_empty_repo">cloning an empty repo</a>
* <a href="#_all_syntax_for_repos">`@all` syntax for repos</a>
* <a href="#_umask_setting">umask setting</a>
* <a href="#_getting_a_tar_file_from_a_clone">getting a tar file from a clone</a>
* <a href="#_features">features</a>
* <a href="#_syntax_and_normal_usage">syntax and normal usage</a>
* <a href="#_simpler_syntax">simpler syntax</a>
* <a href="#_one_user_many_keys">one user, many keys</a>
* <a href="#_security_access_control_and_auditing">security, access control, and auditing</a>
* <a href="#_two_levels_of_access_rights_checking">two levels of access rights checking</a>
* <a href="#_better_logging">better logging</a>
* <a href="#_exclude_or_deny_rules">"exclude" (or "deny") rules</a>
* <a href="#_separating_delete_and_rewind_rights">separating delete and rewind rights</a>
* <a href="#_separating_create_and_push_rights">separating create and push rights</a>
* <a href="#_file_dir_NAME_based_restrictions">file/dir NAME based restrictions</a>
* <a href="#_delegating_parts_of_the_config_file">delegating parts of the config file</a>
* <a href="#_convenience_features">convenience features</a>
* <a href="#_what_repos_do_I_have_access_to_">what repos do I have access to?</a>
* <a href="#_including_config_lines_from_other_files">including config lines from other files</a>
* <a href="#_support_for_git_installed_outside_default_PATH">support for git installed outside default PATH</a>
* <a href="#_personal_branches">"personal" branches</a>
* <a href="#_custom_hooks_and_custom_git_config">custom hooks and custom git config</a>
* <a href="#_bypassing_gitolite">bypassing gitolite</a>
* <a href="#_INconvenience_features">INconvenience features</a>
* <a href="#_deleting_a_repo">deleting a repo</a>
* <a href="#_helping_with_gitweb">helping with gitweb</a>
* <a href="#_easier_to_specify_gitweb_description_and_gitweb_daemon_access">easier to specify gitweb "description" and gitweb/daemon access</a>
* <a href="#_easier_to_link_gitweb_authorisation_with_gitolite">easier to link gitweb authorisation with gitolite</a>
* <a href="#_advanced_features">advanced features</a>
* <a href="#_repos_named_with_wildcards">repos named with wildcards</a>
* <a href="#_admin_defined_commands">admin defined commands</a>
* <a href="#_access_control_for_external_commands">access control for external commands</a>
* <a href="#_svnserve">svnserve</a>
* <a href="#_design_choices">design choices</a>
* <a href="#_keeping_the_parser_and_the_access_control_separate">keeping the parser and the access control separate</a>
----
<a name="_common_errors_and_mistakes"></a>
### 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.
<a name="_git_version_dependency"></a>
### git version dependency
Gitolite (on the server) now refuses to run if git is not at least 1.6.2.
<a name="_other_errors_warnings_notes_"></a>
### other errors, warnings, notes...
<a name="_cloning_an_empty_repo"></a>
#### 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]
<a name="_all_syntax_for_repos"></a>
#### `@all` syntax for repos
There *is* a way to use the `@all` syntax for repos also, as described in
`conf/example.conf`. 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.
* don't try giving `@all` users some permission for `@all` repos
<a name="_umask_setting"></a>
#### umask setting
Gitweb not able to read your repos? You can change the umask for newly
created repos to something more relaxed -- see the `~/.gitolite.rc` file
<a name="_getting_a_tar_file_from_a_clone"></a>
### getting a tar file from a clone
You can clone the repo from github or indefero, then execute a make command to
extract a tar file of the branch you want. Please use the make command, not a
plain "git archive", because the Makefile adds a file called
`.GITOLITE-VERSION` that will help you identify which version you are using.
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"
<a name="_features"></a>
### 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.
<a name="_syntax_and_normal_usage"></a>
#### syntax and normal usage
<a name="_simpler_syntax"></a>
##### simpler syntax
The basic syntax is simpler and cleaner but it goes beyond that: **you can
specify access in bits and pieces**, even if they overlap.
Some access needs are best grouped by repo, some by username, and some by
both. So just do all of them, and gitolite will combine all the access lists!
Here's an example:
# define groups of people
@bosses = phb1 phb2 phb3
@devs = dev1 dev2 dev3
@interns = int1 int2 int3
# define groups of projects
@open = git gitolite linux rakudo
@closed = c1 c2 c3
@topsecret = ts1 ts2 ts3
# 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
[...or any other combination you want...]
# later in the file:
# specify access for individual repos (like RW, RW+, etc)
repo c1
[...]
[...etc...]
If you notice that `@bosses` are given read access to `@open` via both rules,
do not worry that this causes some duplication or inefficiency. It doesn't
:-)
See the "specify gitweb/daemon access" section below for one more example.
<a name="multikeys"></a>
<a name="_one_user_many_keys"></a>
##### one user, many keys
I have a laptop and a desktop I need to access the server from. I have
different private keys on them, but as far as gitolite is concerned both of
them should be treated as "sitaram". How does this work?
The way it works is that you copy one pubkey as "sitaram@laptop.pub" and the
other as "sitaram@desktop.pub". The part before the "@" is the username, so
gitolite knows these two keys belong to the same person. The part after the
"@" can be anything you like, of course; gitolite doesn't care.
Note that you don't say "sitaram@laptop" and so on in the **config** file --
as far as the config file is concerned there's just **one** user called
"sitaram" -- so you only say "sitaram" there.
I think this is easier to maintain if you have to delete or change one of
those keys.
However, now that `sitaramc@gmail.com` is also a valid username, we need to
distinguish between `sitaramc@gmail.com.pub` and `sitaramc@desktop.pub`. We
do that by requiring that the multi-key suffix you use (like "desktop" and
"laptop") should not have a `"."` in it. If it does, it looks like an email
address. The following table lists sample pubkey filenames and the
corresponding derived usernames (which is what goes into the
`conf/gitolite.conf` file):
* old style multikeys; not mistaken for emails because there is no "." in
hostname part
sitaramc.pub sitaramc
sitaramc@laptop.pub sitaramc
sitaramc@desktop.pub sitaramc
* new style, email keys; there is a "." in hostname part; so it's an email
address
sitaramc@gmail.com.pub sitaramc@gmail.com
* multikeys *with* email address
sitaramc@gmail.com@laptop.pub sitaramc@gmail.com
sitaramc@gmail.com@desktop.pub sitaramc@gmail.com
<a name="_security_access_control_and_auditing"></a>
#### security, access control, and auditing
<a name="_two_levels_of_access_rights_checking"></a>
##### 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`) must have
read permission (i.e., R, RW, or RW+) on at least one branch of the repo.
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.
<a name="_better_logging"></a>
##### 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.
<a name="_exclude_or_deny_rules"></a>
##### "exclude" (or "deny") rules
Here is an illustrative explanation of "deny" rules. However, please be sure
to read the "DENY/EXCLUDE RULES" section in `conf/example.conf` for important
notes/caveats before using "deny" rules.
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.
One way to fix this is to allow "excludes" -- some changes in syntax, combined
with a rigorous, ordered, interpretation would do it.
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
<a name="_separating_delete_and_rewind_rights"></a>
##### separating delete and rewind rights
Since the beginning, `RW+` meant being able to rewind *or* delete a ref. My
stand is that these two are fairly similar, and infact 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 them --
situations where one of them should be restricted more than the other.
([Arguments][sdrr] exist for both sides: restrict delete more than rewind, and
vice versa).
So we now allow these two rights to be separated. Here's how:
* branch deletion is permitted by using `RWD` or `RW+D` -- essentially the
current branch permissions with a `D` suffixed
* if a repo has a rule containing such a `D`, all `RW+` permissions (for
that repo) cease to permit deletion of the ref matched.
This provides the *greatest* backward compatibility, while also enabling the
new semantics at the granularity of a repo, instead of the entire config.
Note 1: if you find that `RW+` no longer allows deletion but you can't see a
`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 :)
Note 2: a quick way to make this the default for *all* your repos is:
repo @all
RWD 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.
Note 3: you can combine this with the "create a branch" permissions described
in the next section, as the example line in conf/example.conf shows.
<a name="_separating_create_and_push_rights"></a>
##### separating create and push rights
This feature is similar in spirit to the previous one, so please read that
section for a general understanding.
Briefly:
* branch creation is permitted by using `RWC` or `RW+C` -- essentially the
current branch permissions with a `C` suffixed
* if a repo has a rule containing such a `C`, then the `RW` and `RW+`
permissions (for that repo) no longer permit creation of the ref matched;
they will only allow pushing to an existing ref
Note: you can combine this with the "delete a branch" permissions described in
the previous section, as the example line in conf/example.conf shows.
<a name="_file_dir_NAME_based_restrictions"></a>
##### file/dir NAME based restrictions
In addition to branch-name based restrictions, gitolite also allows you to
restrict what files or directories can be involved in changes being pushed.
This basically uses `git diff --name-only` to obtain the list of files being
changed, treating each filename as a "ref" to be matched.
Please see `conf/example.conf` for syntax and examples.
<a name="_delegating_parts_of_the_config_file"></a>
##### 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][] for details.
<a name="_convenience_features"></a>
#### convenience features
<a name="_what_repos_do_I_have_access_to_"></a>
##### 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` and `expand`) to help you find this
information; please check [doc/report-output.mkd][repout] for details.
<a name="_including_config_lines_from_other_files"></a>
##### including config lines from other files
See the entry under "INCLUDE SOME OTHER FILE" in `conf/example.conf`.
<a name="_support_for_git_installed_outside_default_PATH"></a>
##### 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.<name>.receivepack` and `remote.<name>.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}";
<a name="_personal_branches"></a>
##### "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.
gitolite lets you define a "personal" or "scratch" namespace prefix for each
developer (e.g., `refs/personal/<devname>/*`). Just add a line like:
RW+ personal/USER/ = @userlist
This means I (user "sitaram") can do anything to any branch whose name starts
with `personal/sitaram/` assuming I'm in "userlist".
You can have any number of such lines with different prefixes (for example,
using topic names instead of "personal") or even suffixes if you like. The
important thing is that the "branch" name should contain `/USER/` (including
the slashes). At runtime this will match whoever is the current user. Access
is still determined by the right hand side of course.
<a name="_custom_hooks_and_custom_git_config"></a>
##### 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
`conf/example.conf` for details.
<a name="_bypassing_gitolite"></a>
##### 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
<a name="_INconvenience_features"></a>
#### INconvenience features
<a name="_deleting_a_repo"></a>
##### 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][rmrepo] 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 `$GL_REPO_BASE` to in the `~/.gitolite.rc`)
<a name="_helping_with_gitweb"></a>
#### 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!
<a name="gwd"></a>
<a name="_easier_to_specify_gitweb_description_and_gitweb_daemon_access"></a>
##### easier to specify gitweb "description" and gitweb/daemon access
Please see [gwd] for details on how to do this if you've never done this
before. This section is only about how gitolite makes it easy to specify
different combinations of access for different sets of repos.
[gwd]: http://github.com/sitaramc/gitolite/blob/pu/doc/2-admin.mkd#gwd
Remember gitolite lets you specify the access control specs in bits and
pieces, so you can keep all the daemon/gitweb access in one place, even if
each repo has more specific branch-level access config specified elsewhere.
Here's an example, using really short reponames because I'm lazy:
# maybe near the top of the file, for ease of access:
@only_web = r1 r2 r3
@only_daemon = r4 r5 r6
@web_and_daemon = r7 r8 r9
repo @only_web
R = gitweb
repo @only_daemon
R = daemon
repo @web_and_daemon
R = gitweb
R = daemon
# ...maybe much later in the file:
repo r1
# normal developer access lists for r1 and its branches/tags in the
# usual way
repo r2
# ...and so on...
<a name="_easier_to_link_gitweb_authorisation_with_gitolite"></a>
##### 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/`.
<a name="_advanced_features"></a>
#### advanced features
<a name="_repos_named_with_wildcards"></a>
##### repos named with wildcards
Please see `doc/wildcard-repositories.mkd` for all the details.
<a name="_admin_defined_commands"></a>
##### admin defined commands
This requires the wildcards feature to be enabled, but is then an extremely
powerful feature. See `doc/admin-defined-commands.mkd`.
<a name="_access_control_for_external_commands"></a>
##### access control for external commands
Gitolite now has a mechanism for allowing access control for arbitrary
external commands, as long as they are invoked via ssh and present a
server-side command that contains enough information to make an access control
decision.
Note that this is incompatible with giving people shell access as described in
`doc/ssh-troubleshooting.mkd` -- people who have shell access are not
subject to this mechanism (it wouldn't make sense to try and control someone
who has shell access anyway).
In general, external commands require changes in one or both the config files;
the sample files in `conf/` double as documentation, so you should look there
for examples and usage.
Commands implemented so far are:
* rsync
* svnserve (see next section for a brief description; this has been
contributed by Simon and Vladimir)
<a name="_svnserve"></a>
###### svnserve
If you are transitioning from SVN to gitolite, and have a lot of users using
public-key authentication with SVN, this feature may be useful to you. Once
you migrate all users' public keys into gitolite, you can set the `$SVNSERVE`
variable in `~/.gitolite.rc` to tie `svnserve` with gitolite's authentication
system. Assuming you installed gitolite to the same user as the one you used
for SVN, SVN connectivity will be retained, and users will be able to use
both SVN and git using the same SSH configuration.
<a name="_design_choices"></a>
### design choices
<a name="_keeping_the_parser_and_the_access_control_separate"></a>
#### keeping the parser and the access control separate
There are two programs concerned with access control:
* `gl-auth-command`, the program that is run via `~/.ssh/authorized_keys`;
this decides whether git should even be allowed to run (basic R/W/no
access). (This one cannot decide on the branch-level access; it is not
known at this point what branch is being accessed)
* the update-hook on each repo, which decides the per-branch permissions
I have chosen to keep the relatively complex task of parsing the config file
out of them to keep them simpler (and faster). So any changes to the config
have to be first "compiled", and the access control programs use this
"compiled" version of the config. (The compile step also refreshes
`~/.ssh/authorized_keys`).
If you choose the "easy install" method, all this is quite transparent to you
anyway. If you cannot use the easy install and must install manually, I have
clear instructions on how to set it up.
[repout]: http://github.com/sitaramc/gitolite/blob/pu/doc/report-output.mkd
[sdrr]: http://groups.google.com/group/gitolite/browse_thread/thread/9f2b4358ce406d4c#
[delegation]: http://github.com/sitaramc/gitolite/blob/pu/doc/delegation.mkd
[rmrepo]: http://github.com/sitaramc/gitolite/blob/pu/doc/admin-defined-commands.mkd#rmrepo

164
doc/CHANGELOG Normal file
View file

@ -0,0 +1,164 @@
Major 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...]
- 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 "<hookname>.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

View file

@ -276,3 +276,64 @@ 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 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 PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES. 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.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
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.
<signature of Ty Coon>, 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.

View file

@ -0,0 +1,242 @@
# admin defined commands
**WARNING: Use this feature only if you really really know what you're doing.
If you come back to me saying this feature caused a problem, I will only
laugh. The worse the problem, the louder I will laugh. You won't actually
hear me laughing, but you'll feel it in your bones, trust me!**
There may be other such **WARNING** sections below. **Read all of them**.
----
In this document:
* <a href="#_background">background</a>
* <a href="#_setting_it_up">setting it up</a>
* <a href="#_anatomy_of_a_command">anatomy of a command</a>
* <a href="#_example_uses_and_sample_commands_in_contrib">example uses and sample commands in contrib</a>
* <a href="#_fork">fork</a>
* <a href="#_rmrepo">rmrepo</a>
* <a href="#_enable_disable_push_access_temporarily">enable/disable push access temporarily</a>
* <a href="#_bonus_restricted_admin">(bonus) restricted admin</a>
----
<a name="_background"></a>
### background
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!
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*.
----
It may be a good idea to read [doc/wildcard-repositories.mkd][wild] before
you continue here though, because most of the uses of this feature also need
wildcard repos. (This also means you must set `$GL_WILDREPOS` to "1" in the
rc file).
The wildcard repo feature is a way to create repositories matching a pattern
(even if it as simple as `personal/CREATOR/.+`), and a way to specify two
categories of permissions for each such user-created repo.
What we want now is more than that, as you'll see in the examples below. And
I'm sure if you think of more uses you'll send them to me as "contrib"
entries, right?
<a name="_setting_it_up"></a>
### setting it up
This can only be setup by someone who has shell access to the server. Edit
the rc file and update the `$GL_ADC_PATH` variable to point to, say,
`/home/git/bin/adc`. *Nothing happens unless this variable is set and
pointing to a directory*. Then put in whatever such commands you create into
that directory. If you have a command called "foo" in that directory, then a
user can invoke it by saying:
ssh git@server foo argument list
**WARNING: When gitolite takes control, this directory is checked first, and
if the requested command exists, it is executed. It is therefore quite easy
to inadvertently *hide* some of the "official" commands (like "info",
"expand", "setperms", etc., or worse, say "git-upload-pack"!) by creating
executable files with those names in this directory. So don't do that -- you
have been warned!**
<a name="_anatomy_of_a_command"></a>
### anatomy of a command
You can basically do whatever you want in such a command -- go wild! It's
upto you to check the permissions of *each* repo that the user is manipulating
using your command -- you 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].
In addition, all the arguments of the command are also available to you, so
you can define your own command syntaxes. Gitolite checks these arguments to
make sure they fit a somewhat conservative pattern (see `$REPOPATT_PATT` in
`src/gitolite.pm`), so take that into consideration when designing your
commands and usage.
Finally, you can call gitolite to query ownership and permissions for the
current user (which may not necessarily be the owner). This is done loosely
as follows (don't use this exact code yet though):
perl -I$HOME/.gitolite/src -Mgitolite -e "cli_repo_rights('reponame')"
which will print two space-separated words, something like `_____R__W u1` or
maybe `____@R_@W <gitolite>`. (The former is the response for a wildcard repo
created by user `u1`, the latter is for a non-wildcard repo)
But that's cumbersome. There's a bash shell function called
`get_rights_and_owner` in `contrib/adc/adc.common-functions` that is much more
convenient. See any of the other samples for how to use it.
If you don't like this, roll your own. If you don't like bash, do the eqvt in
your language of choice.
<a name="_example_uses_and_sample_commands_in_contrib"></a>
### example uses and sample commands in contrib
<a name="_fork"></a>
#### fork
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.
<a name="rmrepo"></a>
<a name="_rmrepo"></a>
#### rmrepo
This is one thing that you really could not do before this setup was created.
Use it like this:
ssh git@server rmrepo reponame
The script checks to make sure that the repo being deleted was *created* by
the user invoking it.
<a name="_enable_disable_push_access_temporarily"></a>
#### 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: This needs a specific secondary update hook**. Creating a secondary
update hook is described in the sections on "custom hooks" and "hook chaining"
in doc/2. You need code like this in `update.secondary` (don't forget to
`chmod +x` the file):
#!/bin/bash
for f in $HOME/.gitolite.down $PWD/.gitolite.down
do
if [ -f $f ]
then
echo >&2
echo '*** ABORT ***' >&2
echo >&2
cat $f >&2
exit 1
fi
done
exit 0
<a name="_bonus_restricted_admin"></a>
#### (bonus) restricted admin
It's rather important to me (and presumably others in the "corporate" world)
to separate permission to push to the "gitolite-admin" repo from unrestricted
shell access to the server. This issue has been visited often in the past.
Until now, though, this was binary -- you either had full shell access or none
at all. If there were tasks that legitimately needed to be done from the
shell on the server, it often meant you had to break that separation or load
the few people who did have shell access already.
Now, however, it is possible to provide scripts to do what you want, and put
them in `$GL_ADC_PATH`. `contrib/adc/restrict-admin` is a commented sample --
as you can see, it cleverly makes use of the fact that you can now check for
the invoking uses access to any repo in the system. In this case it checks if
he has "W" access to the gitolite-admin repo, and if he does, allows the
script to proceed.
[Note that this particular use does not require `$GL_WILDREPOS` to be enabled,
because it's not using any wildcard repos].
[xkcd224]: http://xkcd.com/224/
[lazy]: http://c2.com/cgi/wiki?LazinessImpatienceHubris
[wild]: http://github.com/sitaramc/gitolite/blob/pu/doc/wildcard-repositories.mkd

249
doc/big-config.mkd Normal file
View file

@ -0,0 +1,249 @@
# what is a "big-config"
In this document:
* <a href="#_when_why_do_we_need_it_">when/why do we need it?</a>
* <a href="#_how_do_we_use_it_">how do we use it?</a>
* <a href="#_other_optimisations">other optimisations</a>
* <a href="#_what_are_the_downsides_">what are the downsides?</a>
* <a href="#_storing_usergroup_information_outside_gitolite_like_in_LDAP_">storing usergroup information outside gitolite (like in LDAP)</a>
* <a href="#_why">why</a>
* <a href="#_how">how</a>
<a name="_when_why_do_we_need_it_"></a>
### when/why do we need it?
A "big config" is anything that has a few thousand users and a few thousand
repos, organised into groups that are much smaller in number (like maybe a few
hundreds of repogroups and a few dozens of usergroups).
So let's say you have
@wbr = lynx firefox
@devs = alice bob
repo @wbr
RW+ next = @devs
RW master = @devs
Gitolite internally translates this to
repo lynx firefox
RW+ next = alice bob
RW master = alice bob
Not just that -- it now generates the actual config rules once for each
user-repo-ref combination (there are 8 combinations above; the compiled config
file looks partly like this:
%repos = (
'firefox' => {
'R' => {
'alice' => 1,
'bob' => 1
},
'W' => {
'alice' => 1,
'bob' => 1
},
'alice' => [
{
'refs/heads/next' => 'RW+'
},
{
'refs/heads/master' => 'RW'
}
],
'bob' => [
{
'refs/heads/next' => 'RW+'
},
{
'refs/heads/master' => 'RW'
}
]
},
'lynx' => {
'R' => {
'alice' => 1,
'bob' => 1
},
'W' => {
'alice' => 1,
'bob' => 1
},
'alice' => [
{
'refs/heads/next' => 'RW+'
},
{
'refs/heads/master' => 'RW'
}
],
'bob' => [
{
'refs/heads/next' => 'RW+'
},
{
'refs/heads/master' => 'RW'
}
]
}
);
Phew!
You can imagine what that does when you have 10,000 users and 10,000 repos.
Let's just say it's not pretty :)
<a name="_how_do_we_use_it_"></a>
### how do we use it?
Now, if you had all those 10,000 users and repos explicitly listed (no
groups), then there is no help. But if, like the above example, you had
groups like we used above, there is hope.
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, the compiled file
looks like this:
%repos = (
'@wbr' => {
'@devs' => [
{
'refs/heads/next' => 'RW+'
},
{
'refs/heads/master' => 'RW'
}
],
'R' => {
'@devs' => 1
},
'W' => {
'@devs' => 1
}
},
);
%groups = (
'@devs' => {
'alice' => 'master',
'bob' => 'master'
},
'@wbr' => {
'firefox' => 'master',
'lynx' => 'master'
}
);
That's a lot smaller, and allows orders of magintude more repos and groups to
be supported.
<a name="_other_optimisations"></a>
### other optimisations
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 "[easier to specify gitweb
description and gitweb/daemon access][gwd]" for details). This will save a
lot of time when you push the gitolite-admin repo with changes. This variable
also control whether "git config" lines (such as `config hooks.emailprefix =
"[gitolite]"`) will be processed or not.
Setting this is relatively harmless to a normal installation, unlike the next
two variables :-) `GL_NO_CREATE_REPOS` and `GL_NO_SETUP_AUTHKEYS` are meant
for installations where some backend system already exists that does all the
actual repo creation, and all the authentication setup (ssh auth keys),
respectively.
Summary: Please **leave those two variables alone** unless you're initials are
"JK" ;-)
Also note that using all 3 of the `GL_NO_*` variables will result in
*everything* after the config compile being skipped. In other words, gitolite
is being used **only** for its access control language.
<a name="_what_are_the_downsides_"></a>
### what are the downsides?
There is one minor issue.
If you use the delegation feature, you can no longer define or extend
@groups in a fragment, for security reasons. It will also not let you use any
group other than the @fragname itself (specifically, groups which contained a
subset of the allowed @fragname, which would work normally, do not work now).
(If you didn't understand all that, you're probably not using delegation, so
feel free to ignore it!)
<a name="_storing_usergroup_information_outside_gitolite_like_in_LDAP_"></a>
### 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".
<a name="_why"></a>
#### 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.
<a name="_how"></a>
#### 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. [**Help wanted**: I don't
know LDAP, so if someone wants to contribute some sample code I'd be happy to
put it in contrib/, with credit of course!]
Then set the `$GL_GET_MEMBERSHIPS_PGM` variable in the rc file to the full
path to this program, set `$GL_BIG_CONFIG` to 1, and that will be that.
[gwd]: http://github.com/sitaramc/gitolite/blob/pu/doc/3-faq-tips-etc.mkd#gwd

136
doc/delegation.mkd Normal file
View file

@ -0,0 +1,136 @@
# delegating access control responsibilities
[Thanks to jeromeag for forcing me to think through this...]
----
In this document:
* <a href="#_lots_of_repos_lots_of_users">lots of repos, lots of users</a>
* <a href="#_splitting_up_the_set_of_repos_into_groups">splitting up the set of repos into groups</a>
* <a href="#_delegating_ownership_of_groups_of_repos">delegating ownership of groups of repos</a>
* <a href="#_security_philosophy_note">security/philosophy note</a>
----
<a name="_lots_of_repos_lots_of_users"></a>
### 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. Note that delegated admins cannot
create or remove users, not can they define new repos. They can only define
access control rules for a set of repos they have been given authority for.
----
It's easier to show how it all works with an example instead of long
descriptions.
<a name="_splitting_up_the_set_of_repos_into_groups"></a>
### splitting up the set of repos into groups
To start with, recall that gitolite allows you to specify **groups** (of users
or repos, same syntax). So the basic idea is that the main config file
(`conf/gitolite.conf` in your admin repo clone) will specify some repo groups:
# group your projects/repos however you want
@webbrowser_repos = firefox lynx
@webserver_repos = apache nginx
@malware_repos = conficker storm
# any other config as usual, including access control lines for any of the
# above projects or groups
<a name="_delegating_ownership_of_groups_of_repos"></a>
### delegating ownership of groups of repos
Once the repos are grouped, give each person charge of one or more groups.
For example, Alice may be in charge of all web browser development projects,
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
You do this by adding branches to the `gitolite-admin` repo:
# 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/fragments/webbrowser_repos = alice
RW NAME/conf/fragments/webserver_repos = bob
RW NAME/conf/fragments/malware_repos = mallory
This uses gitolite's ability to restrict pushes by file/dir name being changed
-- the syntax you see above ensures that, while "sitaram" does not have any
NAME based restrictions, the other 3 users do. See `conf/example.conf` for
syntax and notes.
As you can see, **for each repo group** you want to delegate authority over,
there's a rule for a **corresponding file** in `conf/fragments` in the
`gitolite-admin` repo. If you have write access to that file, you are allowed
to define rules for repos in that repo group.
In other words, we use gitolite's file/dir NAME-based permissions to "enforce"
the separation between the delegated configs!
Here's how to use this in practice:
* Alice clones the `gitolite-admin` repo, and adds a file called
`conf/fragments/webbrowser_repos.conf`
* she writes in this file any access control rules for the "firefox" and
"lynx" repos. She should not write access rules for any other project --
they will be ignored
* Alice then commits and pushes to the `gitolite-admin` repo
Naturally, a successful push invokes the post-update hook that the admin repo
has, which eventually runs the compile script. The **net effect** is as if
you appended the contents of all the "fragment" files, in alphabetical order,
to the bottom of the main file.
----
<a name="_security_philosophy_note"></a>
### security/philosophy note
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 delegated 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 without all the
"easy install" fuss. See the "system install / user setup" section in
doc/1-INSTALL.mkd for details.

154
doc/gitolite-and-ssh.mkd Normal file
View file

@ -0,0 +1,154 @@
# how gitolite uses ssh
***Gitolite is heavily dependent on ssh***!
Most people didn't realise this, and even if they did they didn'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 :-)
In this document:
* <a href="#_ssh_basics">ssh basics</a>
* <a href="#_how_does_gitolite_use_all_this_ssh_magic_">how does gitolite use all this ssh magic?</a>
* <a href="#_restricting_shell_access_distinguishing_one_user_from_another">restricting shell access/distinguishing one user from another</a>
* <a href="#_restricting_branch_level_actions">restricting branch level actions</a>
----
<a name="_ssh_basics"></a>
### 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.
**This is the backbone of what makes gitolite work; please make sure you
understand this**
<a name="_how_does_gitolite_use_all_this_ssh_magic_"></a>
### 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
<a name="_restricting_shell_access_distinguishing_one_user_from_another"></a>
#### 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 is has 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.
<a name="_restricting_branch_level_actions"></a>
#### 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 y 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.

175
doc/hook-propagation.mkd Normal file
View file

@ -0,0 +1,175 @@
# hook propagation in gitolite
Advanced users need to know how hooks propagate, and when. They also need to
know where to place their hooks, and since there appear to be two places to
put them, what takes precedence. I'll try and set out the logic here.
In this document:
* <a href="#_hooks_used_by_gitolite">hooks used by gitolite</a>
* <a href="#_where_do_I_the_admin_put_the_hooks_">**where** do I (the admin) put the hooks?</a>
* <a href="#_the_from_client_method">the "from-client" method</a>
* <a href="#_the_other_3_methods">the other 3 methods</a>
* <a href="#_the_GL_PACKAGE_HOOKS_directory">the `GL_PACKAGE_HOOKS` directory</a>
* <a href="#_the_HOME_gitolite_directory">the `$HOME/.gitolite` directory</a>
* <a href="#_why_two_places_">why two places?</a>
* <a href="#_special_case_the_non_root_method">special case: the "non-root" method</a>
* <a href="#_when_do_hooks_propagate_">**when** do hooks propagate?</a>
<a name="_hooks_used_by_gitolite"></a>
### hooks used by gitolite
Gitolite uses only 2 hooks. **All** repos have an `update` hook, without
which there is no write-level access control (per-branch permissions). The
special **gitolite-admin** repo has a special `post-update` hook, which is
required to do its, umm, special things, like running the "compile" script,
etc.
In addition there is a "sentinel file" -- an empty file called
"gitolite-hooked". We'll see later what this does.
The final objective of all this is that each repo's `hooks/` directory should
get all the hooks that it is meant to get.
<a name="_where_do_I_the_admin_put_the_hooks_"></a>
### **where** do I (the admin) put the hooks?
In general, **all** hooks go into the `hooks/common` directory. Only the
special `post-update` hook meant for the admin repo goes into
`hooks/gitolite-admin`.
Now we'll discuss the locations of these `hooks/common` and
`hooks/gitolite-admin` directories. This depends on which install method you
used.
(Please refer to [doc/1-INSTALL.mkd][0inst] for what these "methods" are).
<a name="_the_from_client_method"></a>
#### the "from-client" method
Let's get this out of the way first, because it is simple: if you're using the
"from-client" method, there's only one place: the `hooks` directory in your
gitolite clone on the client side. This is where you run
`src/gl-easy-install` from. Nothing else in this section is relevant to this
method; skip to the next section ("when do hooks propagate") if you installed
using the "from-client" method.
<a name="_the_other_3_methods"></a>
#### the other 3 methods
<a name="_the_GL_PACKAGE_HOOKS_directory"></a>
##### the `GL_PACKAGE_HOOKS` directory
You might recall that the "root", and "non-root" methods run a command called
`gl-system-install`, the third argument of which is some directory of your
choice (like maybe `/usr/share/gitolite/hooks`). Even though it is not
necessary to know this, internally this becomes the value of the
`$GL_PACKAGE_HOOKS` variable, so in this document we will refer to that
variable instead of the location (because you might choose any location you
like for it).
The "package" method also has the same property, except that the packager has
already decided what that location is, and the package creation/install
process does the equivalent of `gl-system-install`.
So now we know there's a location called `$GL_PACKAGE_HOOKS` where you can
place your hooks.
<a name="_the_HOME_gitolite_directory"></a>
##### the `$HOME/.gitolite` directory
You might also recall that, in these three methods, each **hosting user** has
to run `gl-setup`. This sets up, among other things, `$HOME/.gitolite`
directory, which also contains a `hooks/` directory.
So now there are two places you can put your hooks, apparently.
<a name="_why_two_places_"></a>
#### why two places?
Just think of the "package" and "root" methods for now, even if you're using
the "non-root" method.
In these two methods, it is reasonable to assume that the entire site (or
server) has certain policies that they want to implement using hooks. They
want to enforce these hooks on *each hosting user*. These hooks go into
`$GL_PACKAGE_HOOKS`.
Each hosting user then has the discretion to add his own hooks (modulo name
clashes, which may necessitate hook chaining, etc., like we already do for the
hooks that gitolite cares about). He adds these hooks to his
`$HOME/.gitolite/hooks` directory.
When hooks propagate, the ones in `$GL_PACKAGE_HOOKS` override/overwrite the
ones in `$HOME/.gitolite/hooks`. Otherwise it wouldn't make sense; you
wouldn't be able to enforce site-wide hooks.
[NOTE: due to a minor quirk, the site-wide hooks in `$GL_PACKAGE_HOOKS` also
get copied to `$HOME/.gitolite/hooks` when you "install". I need to fix and
thoroughly test this later; for now, just ignore the extra files you see in
there; they're harmless/redundant (TODO)]
<a name="_special_case_the_non_root_method"></a>
#### special case: the "non-root" method
This method was created later, just piggy-backing on everything that already
existed to cater to the "package" and "root" methods. In this method, the
`$GL_PACKAGE_HOOKS` is as accessible or under your control as
`$HOME/.gitolite`, so it doesn't matter where you put your hooks. I
*strongly* suggest putting them in `$GL_PACKAGE_HOOKS` and ignoring
`$HOME/.gitolite` completely.
<a name="_when_do_hooks_propagate_"></a>
### **when** do hooks propagate?
First: realise that gitolite *wants to make sure* that all the hooks in your
`hooks/common` directory get copied (symlinked, actually) to *every* repo that
gets created. **Not doing so is generally a security risk; because the
primary purpose of gitolite is access control, people generally *want* hooks
to run.**
Here's how/when hooks are created/propagated:
1. anytime you do an install, gitolite trawls through *all* existing repos
(using the unix `find` command) and force-links all the hooks in all the
repos so they all get the latest and greatest hooks.
2. anytime you do a "compile" (meaning push changes to the admin repo),
gitolite looks through all the repos named in the config. It first checks
if the repo exists, creating it if needed. It then looks for a sentinel
file called "gitolite-hooked" (an empty file in the hooks directory). If
it doesn't find it, it will assume that hooks need to be propagated.
This is because people often copy a repo from elsewhere, add it to the
config, and expect things to work. Without this step, those repos don't
get the hooks, which is bad -- the access control would have failed
silently!
3. anytime a new repo is created, the same force-linking of hooks happens.
The 3 places a new repo is created are:
* the "compile" case mentioned above, where the admin added a normal
repo to the config and pushed
* the wildrepos case, where you have "C" permissions and the repo does
not already exist
* the `fork` command in `contrib/adc`. In this case the hooks are
explicitly copied from the source repo using the `cp` command, not
using the code internal to gitolite.
For people who do not want certain hooks to run for certain repos, one simple
solution that will work right now is to check the value of `$GL_REPO` at the
start of the hook, and `exit 0` based on what it contains/matches.
[0inst]: http://github.com/sitaramc/gitolite/blob/pu/doc/1-INSTALL.mkd

141
doc/http-backend.mkd Normal file
View file

@ -0,0 +1,141 @@
# how to setup gitolite to use smart http mode
In this document:
* <a href="#_WARNINGS_plus_stuff_I_need_help_with">WARNINGS, plus stuff I need help with</a>
* <a href="#_additional_requirements">additional requirements</a>
* <a href="#_detailed_instructions">detailed instructions</a>
* <a href="#_install_gitolite_under_apache_">install gitolite under "apache"</a>
* <a href="#_setup_the_http_backend">setup the http-backend</a>
* <a href="#_usage">usage</a>
----
<a name="_WARNINGS_plus_stuff_I_need_help_with"></a>
### 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-dont-panic) 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 13; 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.
<a name="_additional_requirements"></a>
### 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`.
<a name="_detailed_instructions"></a>
### 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.
<a name="_install_gitolite_under_apache_"></a>
#### 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 13
* 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 first 3 install steps for "non-root" method (clone, mkdir,
and gl-system-install), but **substitute `GITOLITE_HTTP_HOME` in place of
`HOME`** in the mkdir and gl-system-install steps.
**Do NOT run the gl-setup step yet**.
* 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; not 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
<a name="_setup_the_http_backend"></a>
#### setup the http-backend
* when you setup the apache config according to "man git-http-backend",
change these two as below (please note the trailing slash on the
ScriptAlias line):
SetEnv GIT_PROJECT_ROOT /var/www/gitolite-home/repositories
ScriptAlias /git/ /var/www/gitolite-home/bin/gl-auth-command/
You also need this new variable:
SetEnv GITOLITE_HTTP_HOME /var/www/gitolite-home
And that's it... you're done for the setup!
<a name="_usage"></a>
### 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 ;-)
Enjoy!

279
doc/install-transcript.mkd Normal file
View file

@ -0,0 +1,279 @@
# gitolite install transcript
In this document:
* <a href="#_about_this_document">about this document</a>
* <a href="#_create_userids_on_server_and_client_optional_">create userids on server and client (optional)</a>
* <a href="#_get_pubkey_access_from_client_to_server">get pubkey access from client to server</a>
* <a href="#_get_gitolite_source">get gitolite source</a>
* <a href="#_install_gitolite">install gitolite</a>
* <a href="#_VERY_IMPORTANT_">VERY IMPORTANT...</a>
* <a href="#_examine_what_you_have">examine what you have</a>
* <a href="#_emergency_password_access">emergency password access</a>
----
<a name="_about_this_document"></a>
### about this document
This is a *complete* transcript of a full gitolite install, *from scratch*,
using brand new userids ("sita" on the client, "git" on the server). Please
note that you can use existing userids also, it is not necessary to use
dedicated user IDs for this. In particular, people who have a single user
hosting account can also use this method, as long as they have password access
as a fallback if they screw up the keys somewhere. Also, you don't have to
use some *other* server for all this, both server and client can be
"localhost" if you like.
Please note that this entire transcript can be summarised as:
* create users on client and server (optional)
* get pubkey access to server from client (`ssh-copy-id` or manual eqvt)
* run one command ***on client*** (`gl-easy-install`)
...and only that last step is actually gitolite. In fact, the bulk of the
transcript is **non**-gitolite stuff :)
**Please also note that this method will setup everything on the server, but
you have to run it on your workstation, NOT on the server!**
----
<a name="_create_userids_on_server_and_client_optional_"></a>
### create userids on server and client (optional)
Client side: add user, give him a password
sita-lt:~ # useradd sita
sita-lt:~ # passwd sita
Changing password for user sita.
New UNIX password:
Retype new UNIX password:
passwd: all authentication tokens updated successfully.
Server side: (log on to server, then) add user, give it a password
sita-lt:~ # ssh sitaram@server
sitaram@server's password:
Last login: Fri Dec 18 20:25:06 2009
-bash-3.2$ su -
Password:
sita-sv:~ # useradd git
sita-sv:~ # passwd git
Changing password for user git.
New UNIX password:
Retype new UNIX password:
passwd: all authentication tokens updated successfully.
Server side: allow ssh access to "git" user
This is done by editing the sshd config file and adding "git" to the
"AllowUsers" list (the grep command is just confirming the change we made,
because I'm not showing the actual "vi" session):
sita-sv:~ # vim /etc/ssh/sshd_config
sita-sv:~ # grep -i allowusers /etc/ssh/sshd_config
AllowUsers sitaram git
sita-sv:~ # service sshd restart
Stopping sshd: [ OK ]
Starting sshd: [ OK ]
**NOTE**: if the `AllowUsers` setting is completely missing from the sshd
config file, all users are allowed (see `man sshd_config`). You may prefer to
leave it that way -- your choice. I prefer to make the usernames explicit
because I'm paranoid ;-)
----
<a name="_get_pubkey_access_from_client_to_server"></a>
### get pubkey access from client to server
This involves creating a keypair for yourself (using `ssh-keygen`), and
copying the public part of that keypair to the `~/.ssh/authorized_keys` file
on the server (using `ssh-copy-id`, if you're on Linux, or the manual method
described in the `ssh-copy-id` section in `doc/3-faq-tips-etc.mkd`).
sita-lt:~ $ su - sita
Password:
sita@sita-lt:~ $ ssh-keygen
Generating public/private rsa key pair.
Enter file in which to save the key (/home/sita/.ssh/id_rsa):
Created directory '/home/sita/.ssh'.
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /home/sita/.ssh/id_rsa.
Your public key has been saved in /home/sita/.ssh/id_rsa.pub.
The key fingerprint is:
8a:e0:60:1b:04:58:68:50:a4:d7:d0:3a:a5:2d:bf:0a sita@sita-lt.atc.tcs.com
The key's randomart image is:
+--[ RSA 2048]----+
|===. |
|+o oo |
|o..=. |
|..= . |
|.o.+ S |
|.oo... . |
|E.. ... |
| . . |
| .. |
+-----------------+
sita@sita-lt:~ $ ssh-copy-id -i ~/.ssh/id_rsa git@server
git@server's password:
/usr/bin/xauth: creating new authority file /home/git/.Xauthority
Now try logging into the machine, with "ssh 'git@server'", and check in:
.ssh/authorized_keys
to make sure we haven't added extra keys that you weren't expecting.
Double check to make sure you can log on to `git@server` without a password:
sita@sita-lt:~ $ ssh git@server pwd
/home/git
**DO NOT PROCEED UNTIL THIS WORKS OK!**
----
<a name="_get_gitolite_source"></a>
### get gitolite source
sita@sita-lt:~ $ git clone git://github.com/sitaramc/gitolite gitolite-source
Initialized empty Git repository in /home/sita/gitolite-source/.git/
remote: Counting objects: 1157, done.
remote: Compressing objects: 100% (584/584), done.
remote: Total 1157 (delta 756), reused 912 (delta 562)
Receiving objects: 100% (1157/1157), 270.08 KiB | 61 KiB/s, done.
Resolving deltas: 100% (756/756), done.
<a name="_install_gitolite"></a>
### install gitolite
Note that gitolite is installed from the *client*. The `easy-install` script
runs on the client but installs gitolite on the server!
sita@sita-lt:~ $ cd gitolite-source/src
<font color="red"> **This is the only gitolite specific command in a typical
install sequence**. </font> Run it without any arguments to see a usage
message. Run it without the `-q` to get a more verbose, pause-at-every-step,
install mode that allows you to change the defaults (for example, if you want
a different UMASK setting, or you want the repos to be in a different place,
etc.)
sita@sita-lt:src $ ./gl-easy-install -q git server sitaram
you are upgrading (or installing first-time) to v0.95-38-gb0ce84d
setting up keypair...
Generating public/private rsa key pair.
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /home/sita/.ssh/sitaram.
Your public key has been saved in /home/sita/.ssh/sitaram.pub.
The key fingerprint is:
2a:8e:88:42:36:7e:71:e8:cc:ff:4c:54:64:8e:cf:19 sita@sita-lt.atc.tcs.com
The key's randomart image is:
+--[ RSA 2048]----+
| o |
| = |
| . E |
| + o |
| . .S+ |
| + o ... |
|+ = + .. |
|oo B .o |
|+ o o..o |
+-----------------+
creating gitolite para in ~/.ssh/config...
finding/creating gitolite rc...
installing/upgrading...
Initialized empty Git repository in /home/git/repositories/gitolite-admin.git/
Initialized empty Git repository in /home/git/repositories/testing.git/
Pseudo-terminal will not be allocated because stdin is not a terminal.
fatal: No HEAD commit to compare with (yet)
[master (root-commit) 2f40d4b] start
2 files changed, 13 insertions(+), 0 deletions(-)
create mode 100644 conf/gitolite.conf
create mode 100644 keydir/sitaram.pub
cloning gitolite-admin repo...
Initialized empty Git repository in /home/sita/gitolite-admin/.git/
remote: Counting objects: 6, done.
remote: Compressing objects: 100% (4/4), done.
remote: Total 6 (delta 0), reused 0 (delta 0)
Receiving objects: 100% (6/6), done.
---------------------------------------------------------------
done!
Reminder:
*Your* URL for cloning any repo on this server will be
gitolite:reponame.git
*Other* users you set up will have to use
git@server:reponame.git
If this is your first time installing gitolite, please also:
tail -31 ./gl-easy-install
for next steps.
----
<a name="_VERY_IMPORTANT_"></a>
### VERY IMPORTANT...
Please read the text that the easy-install command produces as output when you
run it. People who fail to read this get into trouble later. And I didn't
write all that because I wanted to practice typing.
The text just above this section is an approximation; your version will
contain the correct URLs for your install, including port numbers if
non-standard ports were used).
Try out that `tail -31 ./gl-easy-install` too :)
<a name="_examine_what_you_have"></a>
### examine what you have
The last step of the previous command creates a local clone of your
gitolite-admin repo in `~/gitolite-admin`.
sita@sita-lt:src $ cd ~/gitolite-admin/
sita@sita-lt:gitolite-admin $ git --no-pager log --stat
commit 2f40d4bb80d424dc39aae5d0973f8c1b2e395666
Author: git <git@sita-lt.atc.tcs.com>
Date: Thu Dec 24 21:39:15 2009 +0530
start
conf/gitolite.conf | 12 ++++++++++++
keydir/sitaram.pub | 1 +
2 files changed, 13 insertions(+), 0 deletions(-)
And that's really all. Add keys to keydir here, edit conf/gitolite.conf as
needed, then add, commit, and push the changes to the server.
<a name="_emergency_password_access"></a>
### emergency password access
If you lose your keys or the worst happens and you use the wrong key for the
wrong thing and apparently lose all access, but you still know the password,
this is what you do:
sita@sita-lt:~ $ ssh -o preferredauthentications=password git@server
git@server's password:

105
doc/migrate.mkd Normal file
View file

@ -0,0 +1,105 @@
# migrating from gitosis to gitolite
[TODO: make the migration tool fix up gitweb and daemon control also...]
Migrating from gitosis to gitolite is pretty easy, because the basic design is
the same.
Here's how we migrated my work repos:
1. login as the `git` user on the server, and get a bash shell prompt
2. **disable gitosis** by renaming `/usr/bin/gitosis-serve` to something
else. This will prevent users from pushing anything while you do the
backup, migration, etc.
3. **edit** `~/.ssh/authorized_keys` and **carefully** remove all the lines
containing "gitosis-serve", as well as the marker line that says
"auto-generated by gitosis, DO NOT REMOVE", then save the file. If the
file did not have any other keys and is now empty, don't worry -- save it
anyway because gitolite expects the file to be present (even if it is
empty).
4. 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`.
If you do not do this, an accidental push to the gitosis-admin repo will
mess up your `~/.ssh/authorized_keys` file
5. take a **backup** of the `~/repositories` directory
Now, log off the server and get back to the client:
1. follow instructions to install gitolite; see the [install document][inst].
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.
2. **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`
src/gl-conf-convert < $GSAC/gitosis.conf >> $GLAC/gitolite.conf
Be sure to check the file to make sure it converted correctly
3. **copy** the keys from gitosis's keydir (same meanings for GSAC and GLAC)
cp $GSAC/keydir/* $GLAC/keydir
4. **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.
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][mk] will tell you more about these nuances.
5. **IMPORTANT: expand any multi-key files you may have**. [Here][mk]'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*
6. Check all your changes to your gitolite-admin clone, commit, and push
[mk]: http://github.com/sitaramc/gitolite/blob/pu/doc/3-faq-tips-etc.mkd#multikeys
[inst]: http://github.com/sitaramc/gitolite/blob/pu/doc/1-INSTALL.mkd

313
doc/mirroring.mkd Normal file
View file

@ -0,0 +1,313 @@
# mirroring a gitolite setup
Mirroring git repos is essentially a one-liner. For each mirror you want to
update, you just add a post-receive hook that says
#!/bin/bash
git push --mirror slave_user@mirror.host:/path/to/repo.git
But life is never that simple...
**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`**.
----
In this document:
* <a href="#_RULE_NUMBER_ONE_">RULE NUMBER ONE!</a>
* <a href="#_things_that_will_NOT_be_mirrored_by_this_process">things that will NOT be mirrored by this process</a>
* <a href="#_conventions_in_this_document">conventions in this document</a>
* <a href="#_setting_up_mirroring">setting up mirroring</a>
* <a href="#_install_gitolite_on_all_servers">install gitolite on all servers</a>
* <a href="#_generate_keypairs">generate keypairs</a>
* <a href="#_setup_the_mirror_shell_on_each_server">setup the mirror-shell on each server</a>
* <a href="#_set_slaves_to_slave_mode">set slaves to slave mode</a>
* <a href="#_set_slave_server_lists">set slave server lists</a>
* <a href="#_syncing_the_mirrors_the_first_time">syncing the mirrors the first time</a>
* <a href="#_switching_over">switching over</a>
* <a href="#_the_return_of_foo">the return of foo</a>
* <a href="#_switching_back">switching back</a>
* <a href="#_making_foo_a_slave">making foo a slave</a>
* <a href="#_URLs_that_your_users_will_use">URLs that your users will use</a>
<a name="_RULE_NUMBER_ONE_"></a>
### 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.
<a name="_things_that_will_NOT_be_mirrored_by_this_process"></a>
### things that will NOT be mirrored by this process
Let's get this out of the way. This procedure will only mirror your git
repositories, using `git push --mirror`. Therefore, certain files will not be
mirrored:
* gitolite log files
* "gl-creator" and "gl-perms" files
* "projects.list", "description", and entries in the "config" files within
each repo
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).
Your best bet is to use rsync for the log files, and tar for the others, at
regular intervals.
<a name="_conventions_in_this_document"></a>
### conventions in this document
The userid hosting gitolite is `gitolite` on all machines. The servers are
foo, bar, and baz. At the beginning, foo is the master, the other 2 are
slaves.
<a name="_setting_up_mirroring"></a>
### setting up mirroring
<a name="_install_gitolite_on_all_servers"></a>
#### install gitolite on all servers
* before running the final step in the install sequence, make sure you go to
the `hooks/common` directory and rename `post-receive.mirrorpush` to
`post-receive`. See doc/hook-propagation.mkd if you're not sure where you
should look for `hooks/common`.
* if the server already has gitolite installed, use the normal methods to
make sure this hook gets in.
* Use the same "admin key" on all the machines, so that the same person has
gitolite-admin access to all of them.
<a name="_generate_keypairs"></a>
#### generate keypairs
Each server will be potentially logging on to one or more of the other
servers, so first generate keypairs for all of them (`ssh-keygen`) and copy
the `.pub` files to all other servers, named appropriately. So foo will have
bar.pub and baz.pub, etc.
<a name="_setup_the_mirror_shell_on_each_server"></a>
#### setup the mirror-shell on each server
If you installed gitolite using the from client method, run the following:
# on foo
export GL_ADMINDIR=` cd $HOME;perl -e 'do ".gitolite.rc"; print $GL_ADMINDIR'`
cat bar.pub baz.pub |
sed -e 's,^,command="'$GL_ADMINDIR'/src/gl-mirror-shell" ,' >> ~/.ssh/authorized_keys
If you installed using any of the other 3 methods do this:
cat bar.pub baz.pub |
sed -e 's,^,command="'$(which gl-mirror-shell)'" ,' >> ~/.ssh/authorized_keys
Also do the same thing on the other machines.
Now test this access:
# on foo
ssh gitolite@bar pwd
# should print /home/gitolite/repositories
ssh gitolite@bar uname -a
# should print the appropriate info for that server
Similarly test the other combinations.
<a name="_set_slaves_to_slave_mode"></a>
#### set slaves to slave mode
Set slave mode on all the *slave* servers by setting `$GL_SLAVE_MODE = 1`
(uncommenting the line if necessary).
Leave the master server's file as is.
<a name="_set_slave_server_lists"></a>
#### set slave server lists
On the master (foo), set the names of the slaves by editing the
`~/.gitolite.rc` to contain:
$ENV{GL_SLAVES} = 'gitolite@bar gitolite@baz';
**Note the syntax well; this is critical**:
* **this must be in single quotes** (or you must remember to escape the `@`)
* the variable is an ENV var, not a plain perl var
* the values are *space separated*
* each value represents the userid and hostname for one server
The basic idea is that this string, should be usable in both the following
syntaxes:
git clone gitolite@bar:repo
ssh gitolite@bar pwd
You can also use ssh host aliases. Let's say server "bar" has a non-standard
port number:
# in ~/.ssh/config on foo
host mybar
hostname bar
user gitolite
port 2222
# in ~/.gitolite.rc on foo
$ENV{GL_SLAVES} = 'bar gitolite@baz';
And that's really all there is, unless...
<a name="_syncing_the_mirrors_the_first_time"></a>
### syncing the mirrors the first time
This is fine if you're setting up everything from scratch. But if your master
server already had some repos with commits on them, you have to manually sync
them up once.
# on foo
gl-mirror-sync gitolite@bar
# path to "sync" program is ~/.gitolite/src if "from-client" install
<a name="_switching_over"></a>
### switching over
Let's say foo goes down. You want to make bar the main server, and continue
to have "baz" be a slave.
* on bar, edit `~/.gitolite.rc` and set
$GL_SLAVE_MODE = 0;
$ENV{GL_SLAVES} = 'gitolite@baz';
* **sanity check**: go to your gitolite-admin clone, add a remote for "bar",
fetch it, and make sure they are the same:
git remote add bar gitolite@bar:gitolite-admin
git fetch bar
git branch -a -v
# check that all SHAs are the same
* inform everyone of the new URL for their repos (see next section for more
on this)
* make sure that if "foo" does come up, it will not immediately start
serving requests. You'll be in trouble if (a) foo comes up as it was
before, and (b) some developer still had the old URL lying around and
started pushing changes to it.
You could jump in quickly and set `$GL_SLAVE_MODE = 1` as soon as the
system comes up. Better still, use extraneous means to block incoming
connections from normal users (out of scope for this document).
<a name="_the_return_of_foo"></a>
### the return of foo
<a name="_switching_back"></a>
#### switching back
Switching back is fairly easy.
* synchronise all repos from bar to foo. This may take some time, depending
on how long foo was down.
# on bar
gl-mirror-sync gitolite@foo
# path to "sync" program is ~/.gitolite/src if "from-client" install
* turn off pushes on "bar" by setting slave mode to 1
* run the sync once again; this should complete quickly
* **double check by comparing some the repos on both sides if needed**. You
could run the following snippet on all servers for a quick check:
cd ~/repositories # or wherever $REPO_BASE is
find . -type d -name "*.git" | sort |
while read r
do
echo $r
git ls-remote $r | sort
done | md5sum
* on foo, set the slave list (or check that it is correct)
* on foo, set slave mode off
* tell everyone to switch back
<a name="_making_foo_a_slave"></a>
#### making foo a slave
If "foo" does come up in a controlled manner, you might not want to switch
back right away. Unless you're doing DNS tricks, users may be peeved at
having to do 2 switches.
If you want to make foo a slave, you know the drill by now:
* set slave mode to 1 on foo
* on bar, add foo as a slave
# in ~/.gitolite.rc on bar
$ENV{GL_SLAVES} = 'gitolite@foo gitolite@baz';
I think that should cover pretty much everything. I *have* tested most of
this, but YMMV.
----
<a name="_URLs_that_your_users_will_use"></a>
### URLs that your users will use
Unless you play DNS tricks, it is more than likely that your users would have
to change the URLs they use to access their repos if you change the server
they push to.
I cannot speak for the plethora of git client software out there but for
normal git, this problem can be mitigated somewhat by doing this:
* in `~/.ssh/config` on my workstation, I have
host gl
hostname=primary.server.ip
user=gitolite
* all my `git clone` commands use `gl:reponame` as the URL
* if the primary goes down, and I have to access the secondary, I just
change the `hostname` line in `~/.ssh/config`.
That's it. Every clone of every repo used anywhere in this userid is now
changed.
To repeat, this may or may not work with all the git clients that exist (like
jgit, or any of the GUI tools, and especially if you're on Windows).
If anyone has a better idea, something that works more universally, I'd love
to hear it.

76
doc/mob-branches.mkd Normal file
View file

@ -0,0 +1,76 @@
# 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 13; assumes your gitolite server userid is "gitolite" and
install was "from-client" 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/.gitolite/src/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!

51
doc/overkill.mkd Normal file
View file

@ -0,0 +1,51 @@
# when gitolite is overkill
Note: I wrote this to help people for whom gitolite is genuinely overkill. I
believe it will all work, but YMMV.
----
You don't always need something like gitolite. If you have a fixed (or very
rarely changing) number of users, and all of them have full access to all your
repos, you can use plain Unix permissions to get a lot of this done:
* dedicate a userid (say "git") to host all your repos. This user will also
have a group (normally called "git" on most distros I think)
* create a directory that is accessible (at least "r" and "x" permissions)
to the group "git", all the way upto the root. (That is, if the directory
you chose is /home/git/repos, then /, /home, /home/git, and
/home/git/repos must all be "g+rx").
* create all repos in this directory, as the "git" user, using the following
command:
git init --bare --shared reponame.git
* For each user who needs access to the repos, add them as members to the
"git" group also. On Mandriva this is:
usermod -G git username
Don't forget that `-G` *replaces* the list of supplementary groups for the
user, so be sure to first check if he is already member of some groups and
keep those in the command (comma-separated).
And that's basically it. The "init --shared" will create the repos with
"chmod -R g+s". If you have existing repos where you forgot (or didn't know)
the "--shared" argument, do this on each of them:
cd reponame.git
git init --shared --bare
chmod -R g+w .
chmod g+s `find . -type d`
I think that should do it.
----
You can do more complex things using Unix acls. If you do, and feel like
writing it up, send it to me and I will add it here (with credit given of
course). Personally, I can't be bothered -- once you have differing needs for
different people, you really need gitolite anyway, because you probably need
different rights for branches as well and Unix ACLs can't do that.

56
doc/packaging.mkd Normal file
View file

@ -0,0 +1,56 @@
# 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";
* delete `src/gl-easy-install`; that script is meant for a totally different
mode of installation and does *not* play well in this mode :-)
**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.

184
doc/progit-article.mkd Normal file
View file

@ -0,0 +1,184 @@
## Gitolite ##
Note: the latest copy of this section of the ProGit book is always available within the [gitolite documentation][gldpg]. 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.
[gldpg]: http://github.com/sitaramc/gitolite/blob/pu/doc/progit-article.mkd
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.
<a name="_Installing_"></a>
### 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 4 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 "gitolite host".
People without root access can install it within their own userids. And finally, gitolite can be installed by running a script *on the workstation*, from a bash shell. (Even the bash that comes with msysgit will do, in case you're wondering.)
We will describe this last method in this article; for the other methods please see the documentation.
You start by obtaining public key based access to your server, so that you can log in from your workstation to the server without getting a password prompt. The following method works on Linux; for other workstation OSs you may have to do this manually. We assume you already had a key pair generated using `ssh-keygen`.
$ ssh-copy-id -i ~/.ssh/id_rsa gitolite@gitserver
This will ask you for the password to the gitolite account, and then set up public key access. This is **essential** for the install script, so check to make sure you can run a command without getting a password prompt:
$ ssh gitolite@gitserver pwd
/home/gitolite
Next, you clone Gitolite from the project's main site and run the "easy install" script (the third argument is your name as you would like it to appear in the resulting gitolite-admin repository):
$ git clone git://github.com/sitaramc/gitolite
$ cd gitolite/src
$ ./gl-easy-install -q gitolite gitserver sitaram
And you're done! Gitolite has now been installed on the server, and you now have a brand new repository called `gitolite-admin` in the home directory of your workstation. You administer your gitolite setup by making changes to this repository and pushing.
That last command does produce a fair amount of output, which might be interesting to read. Also, the first time you run this, a new keypair is created; you will have to choose a passphrase or hit enter for none. Why a second keypair is needed, and how it is used, is explained in the "ssh troubleshooting" document that comes with Gitolite. (Hey the documentation has to be good for *something*!)
<a name="_Customising_the_Install_"></a>
### 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, you get a "verbose" mode install -- detailed information on what the install is doing at each step. The verbose mode also allows you to change certain server-side parameters, such as the location of the actual repositories, by editing an "rc" file that the server uses. This "rc" file is liberally commented so you should be able to make any changes you need quite easily, save it, and continue. This file also contains various settings that you can change to enable or disable some of gitolite's advanced features.
<a name="_Config_File_and_Access_Control_Rules_"></a>
### 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 conf/example.conf for details on syntax and features
repo gitolite-admin
RW+ = sitaram
repo testing
RW+ = @all
Notice that "sitaram" (the last argument in the `gl-easy-install` command you gave 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 liberally documented in `conf/example.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.
<a name="_Advanced_Access_Control_with_deny_rules_"></a>
### 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.
<a name="_Restricting_pushes_by_files_changed_"></a>
### 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`.
<a name="_Personal_Branches_"></a>
### 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/<devname>/*`); see the "personal branches" section in `doc/3-faq-tips-etc.mkd` for details.
<a name="_Wildcard_repositories_"></a>
### "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`.
<a name="_Other_Features_"></a>
### 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.

124
doc/report-output.mkd Normal file
View file

@ -0,0 +1,124 @@
# 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 :)
(Side note: if you installed using the "from-client" method, and you're the
administrator, please replace `ssh git@server` with `ssh gitolite`, all
through this document).
In this document:
* <a href="#_the_info_command">the "info" command</a>
* <a href="#_interpreting_the_output">interpreting the output</a>
* <a href="#_using_patterns_to_limit_output">using patterns to limit output</a>
* <a href="#_the_expand_command">the "expand" command</a>
----
<a name="_the_info_command"></a>
### 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, the gitolite version here is v1.5.5-24-g2b066fc
the gitolite config gives you the following access:
R W 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
<a name="_interpreting_the_output"></a>
#### 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.
<a name="_using_patterns_to_limit_output"></a>
#### using patterns to limit output
Here are a couple of samples with optional patterns:
$ ssh git@server info git
hello sitaram, the gitolite version here is v1.5.5-24-g2b066fc
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, the gitolite version here is v1.5.5-24-g2b066fc
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.
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.
<a name="_the_expand_command"></a>
### 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 `<gitolite>` if it was not a wildcard
repo.

71
doc/shell-games.mkd Normal file
View file

@ -0,0 +1,71 @@
# avoiding the shell on the server
Gitolite now tries to prevent gitolite-admin push privileges from being used
to obtain a shell on the server. This was not always the case (older gitolite
did not make this distinction), but I've been moving towards this for a while
now, and, while there could still be holes in that separation, they will be
fixed as and when found.
Thus, settings that have security implications can be set only from the rc
file, which needs to be edited directly on the server. And adding a new hook
requires adding it to the *gitolite* clone and running easy install again, or
gl-setup, if you used the server-side install method, both of which require
shell access.
While this is great for my main target (corporate environments), some people
don't like it. They want to do all of this from the *gitolite-admin* repo,
because the security concern mentioned above does not bother them. They don't
want to log on to the server to make a change in the rc file or don't want to
run easy install to propagate a new set of hooks. In addition, they may want
all of these animals versioned in the "gitolite-admin" repo itself, which
certainly makes sense.
So here's how you might do that.
First, arrange to have all your special files added to the gitolite-admin
repo. The best option is to keep all of this in a single subdirectory (let's
call it "local" in our example). So your `~/.gitolite.rc` might go into
`local/gitolite.rc`, and all your local hooks into `local/hooks` etc. Add
them, commit, and push.
Note: do not create any top level directory called "conf", "contrib", "doc",
"hooks", or "src" -- those names are used by gitolite itself.
Second, create a `post-update.secondary` hook and place it in the *gitolite*
clone's `hooks/common` directory, containing the following code:
#!/bin/bash
GL_ADMINDIR=` cd;perl -e 'do ".gitolite.rc"; print $GL_ADMINDIR'`
cp $GL_ADMINDIR/local/gitolite.rc $HOME/.gitolite.rc
cp -a $GL_ADMINDIR/local/hooks/* $GL_ADMINDIR/hooks/common
$HOME/gitolite-install/src/gl-install -q
Now run easy-install (or gl-setup) once, and you're done.
All future changes to the rc file can be done via local/gitolite.rc in the
admin repo, and hooks can be added to local/hooks.
**Warning**: Nothing in gitolite *removes* hooks, so if you delete (or even
rename) a script, it still stays on the server -- you'll have to delete them
manually from the server.
----
So what's this actually doing?
Well, first, note that `$GL_ADMINDIR` contains files from both gitolite
itself, as well as from the gitolite-admin repo. "conf/VERSION", "src",
"doc", and "hooks" come from gitolite itself, while the other 2 files in
"conf", and all of "keydir" come from the gitolite-admin repo. ("logs"
doesn't come from anywhere).
In addition, any other files in the "master" branch of the gitolite-admin repo
get checked out here, which in this case would mean the entire "local/"
hierarchy you created above.
Now, since the "hooks/common" directory is coming from gitolite itself,
clearly this is where the internal "install" routine expects to find new or
updated hooks to propagate. So you just copy your local hooks (in the
"local/hooks" directory) to "hooks/common" and run the installer again. Done!

468
doc/ssh-troubleshooting.mkd Normal file
View file

@ -0,0 +1,468 @@
# ssh troubleshooting
In this document:
* <a href="#_common_ssh_asks_for_a_password">(common) ssh asks for a password</a>
* <a href="#_problems_when_using_package_root_or_non_root_install_methods">problems when using package, root, or non-root install methods</a>
* <a href="#_problems_when_using_the_from_client_install_method">problems when using the "from-client" install method</a>
* <a href="#_sidebar_why_two_keys_on_client_for_the_admin">(sidebar) why two keys on client for the admin</a>
* <a href="#_bypassing_gitolite_without_intending_to">bypassing gitolite without intending to</a>
* <a href="#_basic_ssh_troubleshooting_for_the_admin">basic ssh troubleshooting for the admin</a>
* <a href="#_windows_issues">windows issues</a>
* <a href="#_details">details</a>
* <a href="#_files_on_the_server">files on the server</a>
* <a href="#_files_on_client">files on client</a>
* <a href="#_some_other_tips_and_tricks">some other tips and tricks</a>
* <a href="#_giving_shell_access_to_gitolite_users">giving shell access to gitolite users</a>
* <a href="#_losing_your_admin_key">losing your admin key</a>
* <a href="#_simulating_ssh_copy_id">simulating ssh-copy-id</a>
----
----
This document should help you troubleshoot ssh-related problems in installing
and accessing gitolite.
**This is about 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.**
**I have spent more than my share of time helping people debug their
misconfigured servers, simply because they tried to blame gitolite for their
troubles. This stops now. I'd rather spend time on actual gitolite features,
code, and documentation.**
Other resources:
* people who think this is too hard should take a look at this
[transcript][] to **see how simple it *actually* is**.
* I **strongly** recommend reading [doc/gitolite-and-ssh.mkd][doc9gas],
which is a very detailed look at how gitolite uses ssh's features on the
server side. Most people don't know ssh as well as they *think* they do;
even if you don't have any problems right now, it's worth skimming over.
* there's a program called `sshkeys-lint` that you can run on your client.
Run it without arguments to get help on how to run it and what inputs it
needs.
----
<a name="_common_ssh_asks_for_a_password"></a>
### (common) ssh asks for a password
**NOTE**: This section should be useful to anyone trying to get password-less
access working. It is **not** specific to gitolite.
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` or `gl-easy-install` steps 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.
* make sure the right private key is being offered. Run ssh in very
verbose mode and look for the word "Offering", like so:
ssh -vvvv 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; see next bullet.
If you don't see any offers being made at all, then you probably don't
have any protocol 2 keys in your `~/.ssh` (names are `id_rsa` or `id_dsa`,
with corresponding `.pub` files).
* 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/mykey` and try
the access again.
* 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.
* 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.
----
<a name="_problems_when_using_package_root_or_non_root_install_methods"></a>
### problems when using package, root, or non-root install methods
This section applies if you installed using any of the [first 3 methods][o3]
of install.
In these 3 modes, installation is done on the server, by logging in as some
other user and doing and `su - git`. The admin's workstation has only one key
that is known to the server's authkeys file, and this key invokes gitolite.
**Note** that this key is not supposed to get you a shell; it is supposed to
invoke gitolite.
As a result, it's a lot easier to debug. Just run `ssh git@server info`. If
this get you the [gitolite version and access info][repout], everything is
fine. If it asks you for a password, see the very first section of this
document for help.
If it gets you the GNU info command output, you have shell access. This
probably means you had passwordless shell access to the server *before* you
were added as a gitolite user, and you sent that same key to your gitolite
admin to include in the admin repo. This won't work -- 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.
You'll have to (create and) use a different keypair for gitolite access.
<a name="_problems_when_using_the_from_client_install_method"></a>
### problems when using the "from-client" install method
This section applies if you installed using the [from-client method][fc].
This method of install is unique in that the admin will have 2 distinct keys
to access the server. The default key (`~/.ssh/id_rsa`) is used to get a
shell prompt and to run commands; for example, `gl-easy-install` uses this key
to do all its server-side work.
In addition, there is a named key created just to invoke gitolite instead of
starting a shell. The name is whatever you gave as the third argument to the
`gl-easy-install` command (for example, `~/.ssh/sitaram.pub` in my case).
Finally, a `host gitolite` para is added to `~/.ssh/config`:
host gitolite
user git
hostname server
identityfile ~/.ssh/sitaram
so that you can use `gitolite:reponame` as the URL to make ssh use the named
key.
All this applies *only* to the admin. Normal users will only have one key and
do not need any of this.
<a name="twokeys"></a>
<a name="_sidebar_why_two_keys_on_client_for_the_admin"></a>
#### (sidebar) why two keys on client for the admin
> There are two types of access the admin will make to the server: a normal
> login, to get a shell prompt, and gitolite access (clone/fetch/push etc).
> The first access needs an authkeys line *without* any "command="
> restrictions, while the second requires a line *with* such a restriction.
> And we can't use the same key for both because there is no way to
> disambiguate them; the ssh server will always (*always*) pick the first
> one in sequence when the key is offered by the ssh client.
> So the next question is usually "I have other ways to get a shell on that
> account (like `su - git` from some other account), so why do I need a key
> for shell access at all?"
> The answer to this is that the "easy install" script, being written for
> the most general case, needs shell access via ssh to do its stuff. If you
> have access otherwise, you really should use one of the other 3 install
> methods to install gitolite. Please see the [install doc][install] for
> details.
<a name="_bypassing_gitolite_without_intending_to"></a>
#### bypassing gitolite without intending to
These problems happen to the person who has **utterly failed** to read/heed
the message that shows up at the end of running the `gl-easy-install` command.
Both these problems are caused by using the wrong key, thus **bypassing
gitolite completely**:
* you get `fatal: 'reponame' does not appear to be a git repository`, and
yet you are sure 'reponame' exists, you haven't mis-spelled it, etc.
* 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).
Let us recap the message that appears on a successful run of the "easy-install"
program; it looks something like this (with suitable values substituted for
`<user>`, `<server>`, and `<port>`):
IMPORTANT NOTE -- PLEASE READ!!!
*Your* URL for cloning any repo on this server will be
gitolite:reponame.git
*Other* users you set up will have to use
<user>@<server>:reponame.git
However, if your server uses a non-standard ssh port, they should use
ssh://<user>@<server>:<port>/reponame.git
If this is your first time installing gitolite, please also:
tail -31 src/gl-easy-install
for next steps.
The first error above happens if you use `git@server:reponame` instead of
`gitolite:reponame`. All your repos are actually in a subdirectory pointed to
by `$REPO_BASE` in the rc file (default: `repositories`). Gitolite internally
prefixes this before calling the actual git command you invoked, but since
you're bypassing gitolite completely, this prefixing does not happen, and so
the repo is not found.
The second error happens if you use `git@server:repositories/reponame.git`
(assuming default `$REPO_BASE` setting) -- that is, you used the full unix
path. Since the "prefixing" mentioned above is not required, the shell finds
the repo and clones ok. But 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.
<a name="_basic_ssh_troubleshooting_for_the_admin"></a>
#### basic ssh troubleshooting for the admin
Otherwise, run these checks:
1. `ssh git@server` should get you a command line *without* asking for a
password.
If it asks you for a password, then your `id_rsa` keypair changed after
you ran the easy install, or someone fiddled with the
`~/.ssh/authorized_keys` file on the server.
If it prints the gitolite version and access info (see
[doc/report-output.mkd][repout]), you managed to overwrite the `id_rsa`
keypair with the `sitaram` keypair, or something equally weird.
2. `ssh gitolite info` should print some gitolite version and access info.
If you get the output of the GNU info command instead, you probably reused
your `id_rsa` keypair as your `sitaram` keypair, or overwrote the
`sitaram` keypair with the `id_rsa` keypair.
There are many ways to fix this, depending on where and what the damage is.
The most generic way (and therefore time-taking) is to re-install gitolite
from scratch:
* make a backup of your gitolite-admin repo clone somewhere (basically your
`keydir/*.pub` and your `conf/gitolite.conf`). If necessary get these
files from the server's `~/.gitolite` directory.
* log on to the server somehow (using some other account, using a password,
su-ing in, etc) and delete `~/.ssh/authorized_keys`. Rename or move aside
`~/.gitolite` so that also looks like it is missing.
* back on your workstation, make sure you have 2 keypairs (`id_rsa` and
`sitaram`, along with corresponding `.pub` files). Create them if needed.
Also make sure they are *different* and not a copy of each other :-)
* install gitolite normally:
* run `ssh-copy-id -i ~/.ssh/id_rsa git@server` to get passwordless
access to the server. (Mac users may have to do this step manually)
* make sure `ssh git@server pwd` prints the `$HOME` of `git@server`
**without** asking for a password. Do not proceed till this works.
* run easy install again, (in my case: `cd gitolite-source;
src/gl-easy-install -q git server sitaram`)
* go to your gitolite-admin repo clone, and copy `conf/gitolite.conf` and
`keydir/*.pub` from your backup to this directory
* copy (be sure to overwrite!) `~/.ssh/sitaram.pub` also to keydir
* now `git add keydir; git commit; git push -f`
That's a long sequence but it should work.
<a name="_windows_issues"></a>
### 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.
If you can offer an *authoritative* account of the complications involved, and
how to resolve them and get things working, I'd be happy to credit you and
include it, either directly here if it is short enough or just an external
link, or in contrib/ if it's a longer piece of text.
<a name="_details"></a>
### details
Here's how it all hangs together.
<a name="_files_on_the_server"></a>
#### files on the server
* the authkeys file; this contains one line containing the pubkey of each
user who is permitted to login without a password.
Pubkey lines that give shell access look like this:
ssh-rsa AAAAB3NzaC[snip]uPjrUiAUew== /home/sitaram/.ssh/id_rsa
On a typical server there will be only one or two of these lines.
Note that the last bit (`/home/sitaram/.ssh/id_rsa`) is purely a *comment*
field and can be anything. Also, the actual lines are much longer, about
400 characters; I snipped 'em in the middle, as you can see.
In contrast, pubkey lines that give access to git repos hosted by gitolite
look like this:
command="[some path]src/gl-auth-command sitaram",[some restrictions] ssh-rsa AAAAB3NzaC[snip]s18OnB42oQ== sitaram@sita-lt
You will have many more of these lines -- one for every pubkey file in
`keydir/` of your gitolite-admin repo, with the corresponding username in
place of "sitaram" in the example above.
The "command=" at the beginning ensures that when someone with the
corresponding private key logs in, they don't get a shell. Instead, the
`gl-auth-command` program is run, and (in this example) is given the
argument `sitaram`. This is how gitolite is invoked, (and is told the
user logging in is "sitaram").
<a name="_files_on_client"></a>
#### files on client
* default keypair; used to get shell access to servers. You would have
copied this pubkey to the gitolite server in order to log in without a
password. (On Linux systems you may have used `ssh-copy-id` to do that).
You would have done this *before* you ran the easy install script, because
otherwise easy install won't run!
~/.ssh/id_rsa
~/.ssh/id_rsa.pub
* gitolite keypair; the "sitaram" in this is the 3rd argument to the
`src/gl-easy-install` command you ran; the easy install script does the
rest
~/.ssh/sitaram
~/.ssh/sitaram.pub
* config file; this file has an entry for gitolite access if you install
usine the "from-client" method. (See above for example "host gitolite"
para in the ssh config file).
This is needed because this is the only way to force git to use a
non-default keypair (unlike command line ssh, which can be given the `-i
~/.ssh/sitaram` flag to do so).
<a name="_some_other_tips_and_tricks"></a>
### some other tips and tricks
<a name="_giving_shell_access_to_gitolite_users"></a>
#### 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 either
cd $HOME/.gitolite # assuming default $GL_ADMINDIR in ~/.gitolite.rc
src/gl-tool shell-add ~/foo.pub
or
gl-tool shell-add ~/foo.pub
The first method is to be used if you used the **user-install** mode, while
the second method is for the **system-install followed by user-setup** mode
(see doc/1-INSTALL.mkd, section on "install methods", for more on this)
**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.
<a name="_losing_your_admin_key"></a>
#### 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, take a look at the
`src/gl-dont-panic` program. You will need shell access to the server of
course. Run it without arguments to get instructions.
<a name="_simulating_ssh_copy_id"></a>
#### 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!)]
[doc9gas]: http://github.com/sitaramc/gitolite/blob/pu/doc/gitolite-and-ssh.mkd
[install]: http://github.com/sitaramc/gitolite/blob/pu/doc/1-INSTALL.mkd
[o3]: http://github.com/sitaramc/gitolite/blob/pu/doc/1-INSTALL.mkd#methods
[fc]: http://github.com/sitaramc/gitolite/blob/pu/doc/1-INSTALL.mkd#fc
[urls]: http://github.com/sitaramc/gitolite/blob/pu/doc/1-INSTALL.mkd#URLs_for_gitolite_managed_repos
[repout]: http://github.com/sitaramc/gitolite/blob/pu/doc/report-output.mkd
[transcript]: http://github.com/sitaramc/gitolite/blob/pu/doc/install-transcript.mkd

67
doc/uninstall.mkd Normal file
View file

@ -0,0 +1,67 @@
# uninstalling gitolite
Sometimes you might find gitolite is overkill -- you have only one user
(yourself) pushing maybe. Or maybe gitolite is just not enough -- you want a
web-based front end that users can use to manage their keys themselves, etc.,
in which case you'd probably switch to [github][g1], [girocco][g2],
[indefero][g3] or [gitorious][g4]. [Gerrit][g5] is quite nice too, if you
want collaborative code review there's nothing like it. Either way, you'd
like to uninstall gitolite.
[g1]: http://github.com
[g2]: http://repo.or.cz/w/girocco.git
[g3]: http://www.indefero.net/
[g4]: http://gitorious.com/
[g5]: http://code.google.com/p/gerrit/
Uninstalling gitolite is fairly easy, although it is manual. (We'll assume
`$REPO_BASE` in the rc file was left at its default of `~/repositories`; if
not, adjust accordingly):
**server side tasks**
* edit `~/.ssh/authorized_keys` and delete the `# gitolite start` and `#
gitolite end` markers and all the lines between them. This will prevent
any of your users from attempting a push while you are doing this.
If you are the only user, and/or *need* one or more of those keys to
continue to access this account (like if one of them is your laptop or
your home desktop etc.) then instead of deleting the line you can just
delete everything upto but not including the words "ssh-rsa" or "ssh-dss".
* Now remove (or move aside or rename to something else if you're paranoid)
the following files and directories.
~/.gitolite
~/.gitolite.rc
~/repositories/gitolite-admin.git
* You can remove all of `~/repositories` if you have not really started
using gitolite properly yet; that's your choice.
If you *do* need to preserve the other repos and continue to use them,
remove all the `update` hooks that git installs on 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.
**client side tasks**
* Any remote users that still have access must update their clone's remote
URLs (edit `.git/config` in the repo) to prefix `repositories/` before the
actual path used, in order for the remote to still work. This is because
you'll now be accessing it through plain ssh, which means you have to give
it the full path.
* Finally, you as the gitolite admin will probably have a host stanza for
"gitolite" in your *client*'s `~/.ssh/config`. Find and delete lines that
look like this:
host gitolite
user git
hostname your.server
port 22
identityfile ~/.ssh/your-gitolite-admin-username

41
doc/who-uses-it.mkd Normal file
View file

@ -0,0 +1,41 @@
# 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
[bc]: http://github.com/sitaramc/gitolite/blob/pu/doc/big-config.mkd
The **KDE project** is currently [testing][kdet] gitolite to see if it will
suit their needs (in combination with redmine for issue tracking and
reviewboard for code review), after an initial [review of alternatives][kdera]
by a core group. Apart from the usual access control, the KDE folks will be
using many of the "ad hoc repo creation" features enabled by wildrepos and the
accompanying commands. Some of the changes to the "admin defined commands"
were inspired by KDE's needs.
[kdet]: http://www.omat.nl/2010/07/07/move-to-git-the-progress-so-far/
[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.
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].
[wild]: http://github.com/sitaramc/gitolite/blob/pu/doc/wildcard-repositories.mkd
[hiren]: http://ece.uwaterloo.ca/~hdpatel/uwhtml/?p=470

View file

@ -0,0 +1,288 @@
# 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 R and RW 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.
----
In this document:
* <a href="#_quick_introduction">quick introduction</a>
* <a href="#_rc_file_setting_required">rc file setting required</a>
* <a href="#_examples_of_wildcard_repos">examples of wildcard repos</a>
* <a href="#_wildcard_repos_with_creator_name_in_them">wildcard repos with creator name in them</a>
* <a href="#_wildcard_repos_without_creator_name_in_them">wildcard repos without creator name in them</a>
* <a href="#_side_note_line_anchored_regexes">side-note: line-anchored regexes</a>
* <a href="#_contrast_with_refexes">contrast with refexes</a>
* <a href="#_handing_out_rights_to_wildcard_matched_repos">handing out rights to wildcard-matched repos</a>
* <a href="#_setting_a_gitweb_description_for_a_wildcard_matched_repo">setting a gitweb description for a wildcard-matched repo</a>
* <a href="#_reporting">reporting</a>
* <a href="#_how_it_actually_works">how it actually works</a>
----
This document is mostly "by example".
----
<a name="_quick_introduction"></a>
### 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.
<a name="_rc_file_setting_required"></a>
### 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 and see comments around it in
`conf/example.gitolite.rc` for more information on this.
<a name="_examples_of_wildcard_repos"></a>
### 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.
<a name="_wildcard_repos_with_creator_name_in_them"></a>
#### 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
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 ;-)
<a name="_wildcard_repos_without_creator_name_in_them"></a>
#### 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.
<a name="_side_note_line_anchored_regexes"></a>
### 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.
<a name="_contrast_with_refexes"></a>
#### contrast with refexes
Just for interest, note that this is in contrast to the refexes for the normal
"branch" permissions, as described in `conf/example.conf` 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.
<a name="_handing_out_rights_to_wildcard_matched_repos"></a>
### 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 place them into one of
two categories (whose permissions are, in this example, R and RW
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
R u5
RW 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:
R u5
RW 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
R u5
RW u6
The following points are important:
* note the syntax of the commands; it's not a "git" command, and there's no
`:` like in a repo URL. The first space-separated word is R or RW, and
the rest are simple usernames.
* whoever you specify as "R" will match the special user READERS. "RW" will
match WRITERS.
<a name="_setting_a_gitweb_description_for_a_wildcard_matched_repo"></a>
### setting a gitweb description for a wildcard-matched repo
Similar to the getperms/setperms commands, there are the getdesc/setdesc
commands, thanks to Teemu.
<a name="_reporting"></a>
### reporting
In order to see what repositories were created from a wildcard, use the
"expand" command, described briefly in [doc/report-output.mkd][repout].
<a name="_how_it_actually_works"></a>
### 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 "RW" permissions using `setperms`, all
occurrences of the word WRITERS are replaced by the invoking username.
Otherwise -- and this includes the "new repo" case, since you couldn't have
run `setperms` on a non-existant repo -- they are replaced by "NOBODY".
(The same thing is done with "R" access and the word "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 (we're
ignoring the "NOBODY" business):
repo assignments/u4/a23
C = @students
RW+ = u4
RW = @TAs
R = @prof
If u4 gives "RW" perms to u5 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.
----
Enjoy, and please use with care. This is pretty powerful stuff. As they say:
if you break it, you get to keep both pieces :)
[repout]: http://github.com/sitaramc/gitolite/blob/pu/doc/report-output.mkd

View file

View file

@ -0,0 +1,21 @@
#!/bin/bash
# gitolite mirroring
# please see doc/mirroring.mkd for instructions on how to use this
if [ -n "$GL_SLAVES" ]
then
for mirror in $GL_SLAVES
do
if git push --mirror $mirror:$GL_REPO.git
then
:
else
ssh $mirror mkdir -p $GL_REPO.git
ssh $mirror git init --bare $GL_REPO.git
git push --mirror $mirror:$GL_REPO.git ||
echo "WARNING: mirror push to $mirror failed"
fi
done
fi >&2

116
hooks/common/update Executable file
View file

@ -0,0 +1,116 @@
#!/usr/bin/perl
use strict;
use warnings;
# === update ===
# this is gitolite's update hook
# part of the gitolite (GL) suite
# how run: via git, being copied as .git/hooks/update in every repo
# when: every push
# input:
# - see man githooks for STDIN
# - uses the compiled config file to get permissions info
# output: based on permissions etc., exit 0 or 1
# security:
# - none
# robustness:
# other notes:
# ----------------------------------------------------------------------------
# common definitions
# ----------------------------------------------------------------------------
our ($GL_CONF_COMPILED, $UPDATE_CHAINS_TO, $GL_PERFLOGT);
our %repos;
# people with shell access should be allowed to bypass the update hook, simply
# by setting an env var that the ssh "front door" will never set
exit 0 if exists $ENV{GL_BYPASS_UPDATE_HOOK};
# we should already have the GL_RC env var set when we enter this hook
die "parse $ENV{GL_RC} failed: " . ($! or $@) unless do $ENV{GL_RC};
require "$ENV{GL_BINDIR}/gitolite.pm";
my ($perm, $creator, $wild) = &repo_rights($ENV{GL_REPO});
my $reported_repo = $ENV{GL_REPO} . ( $ENV{GL_REPOPATT} ? " ($ENV{GL_REPOPATT})" : "" );
# ----------------------------------------------------------------------------
# start...
# ----------------------------------------------------------------------------
my @saved_ARGV = @ARGV; # for chaining to another update hook at the end
my $ref = shift;
my $oldsha = shift;
my $newsha = shift;
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;
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'} || [] };
# 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`;
}
# and in this version, we have many "refs" to check. The one we print in the
# log is the *first* one (which is a *real* ref, like refs/heads/master),
# while all the rest (if they exist) are like NAME/something. So we do the
# first one separately to capture it, then run the rest (if any)
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, so we log the action and exit 0
&log_it("", "$att_acc\t" . substr($oldsha, 0, 14) . "\t" . substr($newsha, 0, 14) .
"\t$reported_repo\t$ref\t$log_refex");
# now chain to the local admin defined update hook, if present
$UPDATE_CHAINS_TO ||= 'hooks/update.secondary';
exec $UPDATE_CHAINS_TO, @saved_ARGV
if -f $UPDATE_CHAINS_TO or -l $UPDATE_CHAINS_TO;
exit 0;

View file

@ -0,0 +1,28 @@
#!/bin/sh
# 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 master
od=$PWD
cd $GL_ADMINDIR
$GL_BINDIR/gl-compile-conf
cd $od
ADMIN_POST_UPDATE_CHAINS_TO=` cd $HOME;perl -e 'do ".gitolite.rc"; print $ADMIN_POST_UPDATE_CHAINS_TO'`
[ -n "$ADMIN_POST_UPDATE_CHAINS_TO" ] || ADMIN_POST_UPDATE_CHAINS_TO=hooks/post-update.secondary
if [ -f $ADMIN_POST_UPDATE_CHAINS_TO ] || [ -L $ADMIN_POST_UPDATE_CHAINS_TO ]
then
exec $ADMIN_POST_UPDATE_CHAINS_TO "$@"
fi

77
install
View file

@ -1,77 +0,0 @@
#!/usr/bin/perl
use strict;
use warnings;
# Clearly you don't need a program to make one measly symlink, but the git
# describe command involved in generating the VERSION string is a bit fiddly.
use Getopt::Long;
use FindBin;
# meant to be run from the root of the gitolite tree, one level above 'src'
BEGIN { $ENV{GL_BINDIR} = $FindBin::RealBin . "/src"; }
BEGIN { $ENV{GL_LIBDIR} = "$ENV{GL_BINDIR}/lib"; }
use lib $ENV{GL_LIBDIR};
use Gitolite::Common;
=for usage
Usage (from gitolite clone directory):
./install
to run gitolite using an absolute or relative path, for example
'src/gitolite' or '/full/path/to/this/dir/src/gitolite'
./install -ln [<dir>]
to symlink just the gitolite executable to some <dir> that is in
$PATH. <dir> defaults to $HOME/bin if <dir> not specified. <dir> is
assumed to exist; gitolite will not create it.
Please provide a full path, not a relative path.
./install -to <dir>
to copy the entire 'src' directory to <dir>. If <dir> is not in
$PATH, use the full path to run gitolite commands.
Please provide a full path, not a relative path.
Simplest use, if $HOME/bin exists and is in $PATH, is:
git clone git://github.com/sitaramc/gitolite
gitolite/install -ln
# now run setup
gitolite setup -pk /path/to/YourName.pub
=cut
my ( $to, $ln, $help, $quiet );
GetOptions(
'to=s' => \$to,
'ln:s' => \$ln,
'help|h' => \$help,
'quiet|q' => \$quiet,
);
usage() if $to and $ln or $help;
$ln = "$ENV{HOME}/bin" if defined($ln) and not $ln;
for my $d ($ln, $to) {
if ($d and not -d $d) {
print STDERR "FATAL: '$d' does not exist.\n";
usage();
}
}
chdir($ENV{GL_BINDIR});
my $version = `git describe --tags --long --dirty=-dt`;
if ($to) {
_mkdir($to);
system("cp -a * $to");
_print( "$to/VERSION", $version );
} elsif ($ln) {
ln_sf( $ENV{GL_BINDIR}, "gitolite", $ln );
_print( "VERSION", $version );
} else {
say "use the following full path for gitolite:";
say "\t$ENV{GL_BINDIR}/gitolite";
_print( "VERSION", $version );
}

View file

@ -1,51 +0,0 @@
#!/bin/sh
# gitolite VREF to count number of changed/new files in a push
# see gitolite docs for what the first 7 arguments mean
# inputs:
# arg-8 is a number
# arg-9 is optional, and can be "NEWFILES"
# outputs (STDOUT)
# arg-7 if the number of changed (or new, if arg-9 supplied) files is > arg-8
# otherwise nothing
# exit status:
# always 0
die() { echo "$@" >&2; exit 1; }
[ -z "$8" ] && die "not meant to be run manually"
newsha=$3
oldtree=$4
newtree=$5
refex=$7
max=$8
nf=
[ "$9" = "NEWFILES" ] && nf='--diff-filter=A'
# NO_SIGNOFF implies NEWFILES
[ "$9" = "NO_SIGNOFF" ] && nf='--diff-filter=A'
# count files against all the other commits in the system not just $oldsha
# (why? consider what is $oldtree when you create a new branch, or what is
# $oldsha when you update an old feature branch from master and then push it
count=`git log --name-only $nf --format=%n $newtree --not --all | grep . | sort -u | perl -ne '}{print "$."'`
[ $count -gt $max ] && {
# count has been exceeded. If $9 was NO_SIGNOFF there's still a chance
# for redemption -- if the top commit has a proper signed-off by line
[ "$9" = "NO_SIGNOFF" ] && {
author_email=$(git log --format=%ae -1 $newsha)
git cat-file -p $newsha |
egrep -i >/dev/null "^ *$count +new +files +signed-off by: *$author_email *$" && exit 0
echo $refex top commit message should include the text \'$count new files signed-off by: $author_email\'
exit 0
}
echo -n $refex "(too many "
[ -n "$nf" ] && echo -n "new " || echo -n "changed "
echo "files in this push)"
}
exit 0

View file

@ -1,66 +0,0 @@
#!/usr/bin/perl
# gitolite VREF to check if all *new* commits have author == pusher
# THIS IS NOT READY TO USE AS IS
# ------------------------------
# you MUST change the 'email_ok()' sub to suit *YOUR* site's
# gitolite username -> author email mapping!
# See bottom of the program for important philosophical notes.
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;
}
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;
# again, we use the trick that a vref can just choose to die instead of
# passing back a vref, having it checked, etc., if it's more convenient
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;
__END__
The following discussion is for people who want to enforce this check on ALL
their developers (i.e., not just the newbies).
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

View file

@ -1,45 +0,0 @@
#!/bin/sh
# gitolite VREF to find autogenerated files
# *completely* site specific; use it as an illustration of what can be done
# with gitolite VREFs if you wish
# see gitolite docs for what the first 7 arguments mean
# inputs:
# arg-8 is currently only one possible value: AUTOGENERATED
# outputs (STDOUT)
# arg-7 if any files changed in the push look like they were autogenerated
# otherwise nothing
# exit status:
# always 0
die() { echo "$@" >&2; exit 1; }
[ -z "$8" ] && die "not meant to be run manually"
newsha=$3
oldtree=$4
newtree=$5
refex=$7
option=$8
[ "$option" = "AUTOGENERATED" ] && {
# currently we only look for ".java" programs with the string "Generated
# by the protocol buffer compiler. DO NOT EDIT" in them.
git log --name-only $nf --format=%n $newtree --not --all |
grep . |
sort -u |
grep '\.java$' |
while read fn
do
git show "$newtree:$fn" | egrep >/dev/null \
'Generated by the protocol buffer compiler. +DO NOT EDIT' ||
continue
echo $refex
exit 0
done
}

View file

@ -1,40 +0,0 @@
#!/usr/bin/perl
use strict;
use warnings;
# gitolite VREF to check max size of new binary files
# see gitolite docs for what the first 7 arguments mean
# inputs:
# arg-8 is a number
# outputs (STDOUT)
# arg-7 if any new binary files exist that are greater in size than arg-8
# *and* there is no "signed-off by" line for such a file in the top commit
# message.
#
# Otherwise nothing
# exit status:
# always 0
die "not meant to be run manually" unless $ARGV[7];
my ( $newsha, $oldtree, $newtree, $refex, $max ) = @ARGV[ 2, 3, 4, 6, 7 ];
# / (.*) +\| Bin 0 -> (\d+) bytes/
chomp( my $author_email = `git log --format=%ae -1 $newsha` );
my $msg = `git cat-file -p $newsha`;
$msg =~ s/\t/ /g; # makes our regexes simpler
for my $newbin (`git diff --stat=999,999 $oldtree $newtree | grep Bin.0.-`) {
next unless $newbin =~ /^ (.*) +\| +Bin 0 -> (\d+) bytes/;
my ( $f, $s ) = ( $1, $2 );
next if $s <= $max;
next if $msg =~ /^ *$f +signed-off by: *$author_email *$/mi;
print "$refex $f is larger than $max";
}
exit 0

View file

@ -1,49 +0,0 @@
#!/usr/bin/perl
use strict;
use warnings;
# gitolite VREF to check if there are any merge commits in the current push.
# THIS IS DEMO CODE; please read all comments below as well as
# doc/vref.mkd before trying to use this.
# usage in conf/gitolite.conf goes like this:
# - VREF/MERGE_CHECK/master = @all
# # reject only if the merge commit is being pushed to the master branch
# - VREF/MERGE_CHECK = @all
# # reject merge commits to any branch
my $ref = $ARGV[0];
my $oldsha = $ARGV[1];
my $newsha = $ARGV[2];
my $refex = $ARGV[6];
# The following code duplicates some code from parse_conf_line() and some from
# check_ref(). This duplication is the only thing that is preventing me from
# removing the "M" permission code from 'core' gitolite and using this
# instead. However, it does demonstrate how you would do this if you had to
# create any other similar features, for example someone wanted "no non-merge
# first-parent", which is far too specific for me to add to 'core'.
# -- begin duplication --
my $branch_refex = $ARGV[7] || '';
if ($branch_refex) {
$branch_refex =~ m(^refs/) or $branch_refex =~ s(^)(refs/heads/);
} else {
$branch_refex = 'refs/.*';
}
exit 0 unless $ref =~ /^$branch_refex/;
# -- end duplication --
# we can't run this check for tag creation or new branch creation, because
# 'git log' does not deal well with $oldsha = '0' x 40.
if ( $oldsha eq "0" x 40 or $newsha eq "0" x 40 ) {
print STDERR "ref create/delete ignored for purposes of merge-check\n";
exit 0;
}
my $ret = `git rev-list -n 1 --merges $oldsha..$newsha`;
print "$refex FATAL: merge commits not allowed\n" if $ret =~ /./;
exit 0;

View file

@ -1,80 +0,0 @@
#!/bin/sh
# gitolite VREF to count votes before allowing pushes to certain branches.
# This approximates gerrit's voting (but it is SHA based; I believe Gerrit is
# more "changeset" based). Here's how it works:
# - A normal developer "bob" proposes changes to master by pushing a commit to
# "pers/bob/master", then informs the voting members by email.
# - Some or all of the voting members fetch and examine the commit. If they
# approve, they "vote" for the commit like so. For example, say voting
# member "alice" fetched bob's proposed commit into "bob-master" on her
# clone, then tested or reviewed it. She would approve it by running:
# git push origin bob-master:votes/alice/master
# - Once enough votes have been tallied (hopefully there is normal team
# communication that says "hey I approved your commit", or it can be checked
# by 'git ls-remote origin' anyway), Bob, or any developer, can push the
# same commit (same SHA) to master and the push will succeed.
# - Finally, a "trusted" developer can push a commit to master without
# worrying about the voting restriction at all.
# The config for this example would look like this:
# repo foo
# # allow personal branches (to submit proposed changes)
# RW+ pers/USER/ = @devs
# - pers/ = @all
#
# # allow only voters to vote
# RW+ votes/USER/ = @voters
# - votes/ = @all
#
# # normal access rules go here; should allow *someone* to push master
# RW+ = @devs
#
# # 2 votes required to push master, but trusted devs don't have this restriction
# RW+ VREF/VOTES/2/master = @trusted-devs
# - VREF/VOTES/2/master = @devs
# Note: "2 votes required to push master" means at least 2 refs matching
# "votes/*/master" have the same SHA as the one currently being pushed.
# ----------------------------------------------------------------------
# see gitolite docs for what the first 7 arguments mean
# inputs:
# arg-8 is a number; see below
# arg-9 is a simple branch name (i.e., "master", etc). Currently this code
# does NOT do vote counting for branch names with more than one component
# (like foo/bar).
# outputs (STDOUT)
# nothing
# exit status:
# always 0
die() { echo "$@" >&2; exit 1; }
[ -z "$8" ] && die "not meant to be run manually"
ref=$1
newsha=$3
refex=$7
votes_needed=$8
branch=$9
# nothing to do if the branch being pushed is not "master" (using our example)
[ "$ref" = "refs/heads/$branch" ] || exit 0
# find how many votes have come in
votes=`git for-each-ref refs/heads/votes/*/$branch | grep -c $newsha`
# send back a vref if we don't have the minimum votes needed. For trusted
# developers this will invoke the RW+ rule and pass anyway, but for others it
# will invoke the "-" rule and fail.
[ $votes -ge $votes_needed ] || echo $refex "require at least $votes_needed votes to push $branch"
exit 0

View file

@ -1,36 +0,0 @@
#!/usr/bin/perl
use strict;
use warnings;
use lib $ENV{GL_LIBDIR};
use Gitolite::Common;
# gitolite VREF to lock and unlock (binary) files. Requires companion command
# 'lock' to be enabled; see doc/locking.mkd for details.
# ----------------------------------------------------------------------
# see gitolite docs for what the first 7 arguments mean
die "not meant to be run manually" unless $ARGV[6];
my $ff = "$ENV{GL_REPO_BASE}/$ENV{GL_REPO}.git/gl-locks";
exit 0 unless -f $ff;
our %locks;
my $t = slurp($ff);
eval $t;
_die "do '$ff' failed with '$@', contact your administrator" if $@;
my ( $oldtree, $newtree, $refex ) = @ARGV[ 3, 4, 6 ];
for my $file (`git diff --name-only $oldtree $newtree` ) {
chomp($file);
if ($locks{$file} and $locks{$file}{USER} ne $ENV{GL_USER}) {
print "$refex '$file' locked by '$locks{$file}{USER}'";
last;
}
}
exit 0

View file

@ -1,41 +0,0 @@
#!/bin/sh
# push updated branches back to the "main" repo.
# This must be run as the *last* VREF, though it doesn't matter what
# permission you give to it
die() { echo "$@" >&2; exit 1; }
repo=$GL_REPO
user=$GL_USER
ref=$1 # we're running like an update hook
old=$2
new=$3
# never send any STDOUT back, to avoid looking like a ref. If we fail, git
# will catch it by our exit code
exec >&2
main=`git config --file $GL_REPO_BASE/$repo.git/config --get gitolite.partialCopyOf`;
[ -z "$main" ] && exit 0
rand=$$
export GL_BYPASS_ACCESS_CHECKS=1
if [ "$new" = "0000000000000000000000000000000000000000" ]
then
# special case for deleting a ref (this is why it is important to put this
# VREF as the last one; if we got this far he is allowed to delete it)
git push -f $GL_REPO_BASE/$main.git :$ref || die "FATAL: failed to delete $ref"
exit 0
fi
git push -f $GL_REPO_BASE/$main.git $new:refs/partial/br-$rand || die "FATAL: failed to send $new"
cd $GL_REPO_BASE/$main.git
git update-ref -d refs/partial/br-$rand
git update-ref $ref $new $old || die "FATAL: update-ref for $ref failed"
exit 0

View file

@ -1,75 +0,0 @@
#!/usr/bin/perl
use strict;
use warnings;
my $rule = $ARGV[7];
die "\n\nFATAL: GL_REFEX_EXPR_ doesn't exist\n(your admin probably forgot the rc file change needed for this to work)\n\n"
unless exists $ENV{"GL_REFEX_EXPR_" . $rule};
my $res = $ENV{"GL_REFEX_EXPR_" . $rule} || 0;
print "$ARGV[6] ($res)\n" if $res;
exit 0;
__END__
Documentation for the refex-expression evaluation feature
First, make sure you have both the VREF and the trigger scripts
(src/VREF/refex-expr and src/lib/Gitolite/Triggers/RefexExpr.pm)
Next, add this to the ACCESS_2 list in the rc file:
'RefexExpr::access_2',
For the rest, we'll use this example:
* user u1 can push foo to some other branch, and anything else to the master
branch, but not foo to the master branch
* user u2 is allowed to push either 'doc/' or 'src/' but not both
Here's the conf file extract:
repo testing
RW+ master = u1 # line 1
RW+ = @all # line 2
RW+ VREF/NAME/foo = u1
RW+ VREF/NAME/doc/ = u2
RW+ VREF/NAME/src/ = u2
# set up 2 refex expressions, named e1, e2
option refex-expr.e1 = master and VREF/NAME/foo
option refex-expr.e2 = VREF/NAME/doc/ and VREF/NAME/src/
# now deny users if the corresponding expression is true
- VREF/refex-expr/e1 = u1
- VREF/refex-expr/e2 = u2
Here are some IMPORTANT notes:
* You MUST place VREF/refex-expr rules at the end. (Only 'partial-copy', if
you use it, must come later).
* You MUST explicitly permit the refexes used in your refex expressions. If
you have more generic rules, the specific ones must come first.
For example, without line 1, the refex recorded for user u1 will come from
line 2, (so it will be 'refs/.*'), and 'master' in the refex expressions
will never have a true value.
* (corollary) make sure you use the exact same refex in the expression as
you did on the original rule line. E.g., a missing slash at the end will
mess things up.
* You can use any logical expression using refexes as operands and using
these operators:
and not xor or
Parens are not allowed.
If a refex has passed, it will have a 'true' value, else it will be false.
The result of the evaluation, after these substitutions, will be the
result of the refex-expr VREF.

View file

@ -1,131 +0,0 @@
#!/bin/sh
# ----------------------------------------------------------------------
# ADMINISTRATOR NOTES:
# ----------------------------------------------------------------------
# - set TRASH_CAN in the rc if you don't like the default. It should be
# relative to GL_REPO_BASE or an absolute value. It should also be on the
# same filesystem as GL_REPO_BASE, otherwise the 'mv' will take too long.
# - you could set TRASH_SUFFIX also but I recomend you leave it as it is
# - run a cron job to delete old repos based on age (the TRASH_SUFFIX has a
# timestamp); your choice how/how often you do that
# - you can completely disable the 'rm' command by setting an rc variable
# called D_DISABLE_RM to "1".
# ----------------------------------------------------------------------
# ----------------------------------------------------------------------
# Usage: ssh git@host D <subcommand> <argument>
#
# The whimsically named "D" command deletes repos ("D" is a counterpart to the
# "C" permission which lets you create repos. Which also means that, just
# like "C", it only works for wild repos).
#
# There are two kinds of deletions: 'rm' removes a repo completely, while
# 'trash' moves it to a trashcan which can be recovered later (upto a time
# limit that your admin will tell you).
#
# The 'rm', 'lock', and 'unlock' subcommands:
# Initially, all repos are "locked" against 'rm'. The correct sequence is
# ssh git@host D unlock repo
# ssh git@host D rm repo
# Since the initial condition is always locked, the "lock" command is
# rarely used but it is there if you want it.
#
# The 'trash', 'list-trash', and 'restore' subcommands:
# You can 'trash' a repo, which moves it to a special place:
# ssh git@host D trash repo
# You can then 'list-trash'
# ssh git@host D list-trash
# which prints something like
# repo/2012-04-11_05:58:51
# allowing you to restore by saying
# ssh git@host D restore repo/2012-04-11_05:58:51
die() { echo "$@" >&2; exit 1; }
usage() { perl -lne 'print substr($_, 2) if /^# Usage/../^$/' < $0; exit 1; }
[ -z "$1" ] && usage
[ "$1" = "-h" ] && usage
[ "$1" != "list-trash" ] && [ -z "$2" ] && usage
[ -z "$GL_USER" ] && die GL_USER not set
# ----------------------------------------------------------------------
cmd=$1
repo=$2
# ----------------------------------------------------------------------
RB=`gitolite query-rc GL_REPO_BASE`; cd $RB
TRASH_CAN=`gitolite query-rc TRASH_CAN`; tcan=Trash; TRASH_CAN=${TRASH_CAN:-$tcan}
TRASH_SUFFIX=`gitolite query-rc TRASH_SUFFIX`; tsuf=`date +%Y-%m-%d_%H:%M:%S`; TRASH_SUFFIX=${TRASH_SUFFIX:-$tsuf}
# ----------------------------------------------------------------------
owner_or_die() {
gitolite creator "$repo" $GL_USER || die You are not authorised
}
# ----------------------------------------------------------------------
if [ "$cmd" = "rm" ]
then
gitolite query-rc -q D_DISABLE_RM && die "sorry, 'unlock' and 'rm' are disabled"
owner_or_die
[ -f $repo.git/gl-rm-ok ] || die "'$repo' is locked!"
rm -rf $repo.git
echo "'$repo' is now gone!"
elif [ "$cmd" = "lock" ]
then
owner_or_die
rm -f $repo.git/gl-rm-ok
echo "'$repo' is now locked"
elif [ "$cmd" = "unlock" ]
then
gitolite query-rc -q D_DISABLE_RM && die "sorry, 'unlock' and 'rm' are disabled"
owner_or_die
touch $repo.git/gl-rm-ok
echo "'$repo' is now unlocked"
elif [ "$cmd" = "trash" ]
then
owner_or_die
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"
elif [ "$cmd" = "list-trash" ]
then
cd $TRASH_CAN 2>/dev/null || exit 0
find . -name gl-creator | sort | while read t
do
owner=
owner=`cat "$t"`
[ "$owner" = "$GL_USER" ] && dirname $t
done | cut -c3-
elif [ "$cmd" = "restore" ]
then
owner=
owner=`cat $TRASH_CAN/$repo/gl-creator 2>/dev/null`
[ "$owner" = "$GL_USER" ] || die "'$repo' is not yours!"
cd $TRASH_CAN
realrepo=`dirname $repo`
[ -d $RB/$realrepo.git ] && die "'$realrepo' already exists"
mv $repo $RB/$realrepo.git
echo "'$repo' restored to '$realrepo'"
else
die "unknown subcommand '$cmd'"
fi

View file

@ -1,74 +0,0 @@
#!/usr/bin/perl
use strict;
use warnings;
use lib $ENV{GL_LIBDIR};
use Gitolite::Rc;
use Gitolite::Common;
use Gitolite::Conf::Load;
=for usage
Usage: gitolite access [-q] <repo> <user> <perm> <ref>
Print access rights for arguments given. The string printed has the word
DENIED in it if access was denied. With '-q', returns only an exit code
(shell truth, not perl truth -- 0 is success).
- repo: mandatory
- user: mandatory
- perm: defauts to '+'. Valid values: R, W, +, C, D, M
- ref: defauts to 'any'. See notes below
Notes:
- ref: Any fully qualified ref ('refs/heads/master', not 'master') is fine.
The 'any' ref is special -- it ignores deny rules (see docs for what this
means and exceptions).
For each case where access is not denied, one line is printed like this:
reponame<tab>username<tab>access rights
This is orders of magnitude faster than running the command multiple times;
you'll notice if you have more than a hundred or so repos.
Advanced uses: see src/triggers/post-compile/update-git-daemon-access-list for
a good example.
=cut
usage() if not @ARGV or $ARGV[0] eq '-h';
my $quiet = 0;
if ( $ARGV[0] eq '-q' ) { $quiet = 1; shift @ARGV; }
my ( $repo, $user, $aa, $ref ) = @ARGV;
$aa ||= '+';
$ref ||= 'any';
_die "invalid perm" if not( $aa and $aa =~ /^(R|W|\+|C|D|M|\^C)$/ );
_die "invalid ref name" if not( $ref and $ref =~ $REPONAME_PATT );
my $ret = '';
if ( $repo ne '%' and $user ne '%' ) {
# single repo, single user; no STDIN
$ret = access( $repo, $user, $aa, $ref );
if ( $ret =~ /DENIED/ ) {
print "$ret\n" unless $quiet;
exit 1;
}
print "$ret\n" unless $quiet;
exit 0;
}
$repo = '' if $repo eq '%';
$user = '' if $user eq '%';
_die "'-q' doesn't go with using a pipe" if $quiet;
@ARGV = ();
while (<>) {
my @in = split;
my $r = $repo || shift @in;
my $u = $user || shift @in;
$ret = access( $r, $u, $aa, $ref );
print "$r\t$u\t$ret\n";
}

View file

@ -1,15 +0,0 @@
#!/bin/bash
# Usage: ssh git@host create <repo>
#
# Create wild repo.
die() { echo "$@" >&2; exit 1; }
usage() { perl -lne 'print substr($_, 2) if /^# Usage/../^$/' < $0; exit 1; }
[ -z "$1" ] && usage
[ -z "$2" ] || usage
[ "$1" = "-h" ] && usage
[ -z "$GL_USER" ] && die GL_USER not set
# ----------------------------------------------------------------------
exec $GL_BINDIR/commands/perms -c "$@" < /dev/null

View file

@ -1,40 +0,0 @@
#!/usr/bin/perl
use strict;
use warnings;
use lib $ENV{GL_LIBDIR};
use Gitolite::Rc;
use Gitolite::Common;
use Gitolite::Conf::Load;
=for usage
Usage: gitolite creator [-n] <reponame> [<username>]
Print the creator name for the repo. A '-n' suppresses the newline.
When an optional username is supplied, it checks if the user is the creator of
the repo and returns an exit code (shell truth, 0 for success) instead of
printing anything, which makes it possible to do this in shell:
if gitolite creator someRepo someUser
then
...
=cut
usage() if not @ARGV or $ARGV[0] eq '-h';
my $nl = "\n";
if ( $ARGV[0] eq '-n' ) {
$nl = '';
shift;
}
my $repo = shift;
my $user = shift || '';
my $creator = '';
$creator = creator($repo) if not repo_missing($repo);
if ($user) {
exit 0 if $creator eq $user;
exit 1;
}
return ( $creator eq $user ) if $user;
print "$creator$nl";

View file

@ -1,43 +0,0 @@
#!/bin/sh
# Usage: ssh git@host desc <repo>
# ssh git@host desc <repo> <description string>
#
# Show or set description for user-created ("wild") repo.
die() { echo "$@" >&2; exit 1; }
usage() { perl -lne 'print substr($_, 2) if /^# Usage/../^$/' < $0; exit 1; }
[ -z "$1" ] && usage
[ "$1" = "-h" ] && usage
[ -z "$GL_USER" ] && die GL_USER not set
# ----------------------------------------------------------------------
repo=$1; shift
# this shell script takes arguments that are completely under the user's
# control, so make sure you quote those suckers!
# kernel.org needs 'desc' to be available to people who have "RW" or above,
# not just the "creator". In fact they need it for non-wild repos so there
# *is* no creator.
if gitolite query-rc -q WRITER_CAN_UPDATE_DESC
then
gitolite access -q "$repo" $GL_USER W any || die You are not authorised
else
gitolite creator "$repo" $GL_USER || die You are not authorised
fi
# if it passes, $repo is a valid repo name so it is known to contain only sane
# characters. This is because 'gitolite creator' return true only if there
# *is* a repo of that name and it has a gl-creator file that contains the same
# text as $GL_USER.
descfile=`gitolite query-rc GL_REPO_BASE`/"$repo".git/description
if [ -z "$1" ]
then
[ -r "$descfile" ] && cat "$descfile"
exit 0
fi
echo "$*" > "$descfile"

View file

@ -1,59 +0,0 @@
#!/bin/sh
# Usage: ssh git@host fork <repo1> <repo2>
#
# Forks repo1 to repo2. You must have read permissions on repo1, and create
# ("C") permissions for repo2, which of course must not exist.
#
# A fork is functionally the same as cloning repo1 to a client and pushing it
# to a new repo2. It's just a little more efficient, not just in network
# traffic but because it uses git clone's "-l" option to share the object
# store also, so it is likely to be almost instantaneous, regardless of how
# big the repo actually is.
die() { echo "$@" >&2; exit 1; }
usage() { perl -lne 'print substr($_, 2) if /^# Usage/../^$/' < $0; exit 1; }
[ -z "$1" ] && usage
[ "$1" = "-h" ] && usage
[ -z "$GL_USER" ] && die GL_USER not set
# ----------------------------------------------------------------------
from=$1; shift
to=$1; shift
[ -z "$to" ] && usage
gitolite access -q "$from" $GL_USER R any || die "'$from' does not exist or you are not allowed to read it"
gitolite access -q "$to" $GL_USER ^C any || die "'$to' already exists or you are not allowed to create it"
# ----------------------------------------------------------------------
# IMPORTANT NOTE: checking whether someone can create a repo is done as above.
# However, make sure that the env var GL_USER is set, and that too to the same
# value as arg-2 of the access command), otherwise it won't work.
# Ideally, you'll leave such code to me. There's a reason ^C is not listed in
# the help message for 'gitolite access'.
# ----------------------------------------------------------------------
# clone $from to $to
git clone --bare -l $GL_REPO_BASE/$from.git $GL_REPO_BASE/$to.git
[ $? -ne 0 ] && exit 1
echo "$from forked to $to" >&2
# fix up creator, default role permissions (gl-perms), and hooks
cd $GL_REPO_BASE/$to.git
echo $GL_USER > gl-creator
touch gl-perms
if gitolite query-rc -q DEFAULT_ROLE_PERMS
then
gitolite query-rc DEFAULT_ROLE_PERMS > gl-perms
fi
ln -sf `gitolite query-rc GL_ADMIN_BASE`/hooks/common/* hooks
# record where you came from
echo "$from" > gl-forked-from
# trigger post_create
gitolite trigger POST_CREATE $to $GL_USER fork

View file

@ -1,86 +0,0 @@
#!/usr/bin/perl
use strict;
use warnings;
use Getopt::Long;
use lib $ENV{GL_LIBDIR};
use Gitolite::Rc;
use Gitolite::Common;
use Gitolite::Conf::Load;
=for usage
Usage: gitolite git-config [-n] [-q] [-r] <repo> <key|pattern>
Print git config keys and values for the given repo. The key is either a full
key, or, if '-r' is supplied, a regex that is applied to all available keys.
-q exit code only (shell truth; 0 is success)
-n suppress trailing newline when used as key (not pattern)
-r treat key as regex pattern (unanchored)
Examples:
gitolite git-config repo gitweb.owner
gitolite git-config -q repo gitweb.owner
gitolite git-config -r repo gitweb
When the key is treated as a pattern, prints:
reponame<tab>key<tab>value<newline>
Otherwise the output is just the value.
Finally, see the advanced use section of 'gitolite access -h' -- you can do
something similar here also:
gitolite list-phy-repos | gitolite git-config -r % gitweb\\. | cut -f1 > ~/projects.list
=cut
usage() if not @ARGV;
my ( $help, $nonl, $quiet, $regex ) = (0) x 4;
GetOptions(
'n' => \$nonl,
'q' => \$quiet,
'r' => \$regex,
'h' => \$help,
) or usage();
my ( $repo, $key ) = @ARGV;
usage() unless $key;
my $ret = '';
if ( $repo ne '%' and $key ne '%' ) {
# single repo, single key; no STDIN
$key = "^\Q$key\E\$" unless $regex;
$ret = git_config( $repo, $key );
# if the key is not a regex, it should match at most one item
_die "found more than one entry for '$key'" if not $regex and scalar( keys %$ret ) > 1;
# unlike access, there's nothing to print if we don't find any matching keys
exit 1 unless %$ret;
if ($regex) {
map { print "$repo\t$_\t" . $ret->{$_} . "\n" } sort keys %$ret unless $quiet;
} else {
map { print $ret->{$_} . ( $nonl ? "" : "\n" ) } sort keys %$ret unless $quiet;
}
exit 0;
}
$repo = '' if $repo eq '%';
$key = '' if $key eq '%';
_die "'-q' doesn't go with using a pipe" if $quiet;
@ARGV = ();
while (<>) {
my @in = split;
my $r = $repo || shift @in;
my $k = $key || shift @in;
$k = "^\Q$k\E\$" unless $regex;
$ret = git_config( $r, $k );
next unless %$ret;
map { print "$r\t$_\t" . $ret->{$_} . "\n" } sort keys %$ret;
}

View file

@ -1,42 +0,0 @@
#!/usr/bin/perl
use strict;
use warnings;
use lib $ENV{GL_LIBDIR};
use Gitolite::Rc;
use Gitolite::Common;
=for usage
Usage: ssh git@host help # via ssh
gitolite help # directly on server command line
Prints a list of custom commands available at this gitolite installation.
Each command has its own help, accessed by passing it '-h' again.
=cut
usage() if @ARGV;
my $user = $ENV{GL_USER} || '';
print "hello" . ( $user ? " $user" : "" ) . ", this is gitolite3 " . version() . " on git " . substr( `git --version`, 12 ) . "\n";
print "list of " . ( $user ? "remote" : "gitolite" ) . " commands available:\n\n";
my %list = (list_x( $ENV{GL_BINDIR}), list_x($rc{LOCAL_CODE} || ''));
for (sort keys %list) {
print "\t$list{$_}" if $ENV{D};
print "\t$_\n" if not $user or $rc{COMMANDS}{$_};
}
print "\n";
exit 0;
# ------------------------------------------------------------------------------
sub list_x {
my $d = shift;
return unless $d;
return unless -d "$d/commands";
_chdir "$d/commands";
return map { $_ => $d } grep { -x $_ } map { chomp; s(^./)(); $_ } `find . -type f -o -type l|sort`;
}

View file

@ -1,44 +0,0 @@
#!/usr/bin/perl
use strict;
use warnings;
use lib $ENV{GL_LIBDIR};
use Gitolite::Rc;
use Gitolite::Common;
=for usage
Usage: ssh git@host htpasswd
Sets your htpasswd, assuming your admin has enabled it.
(Admins: You need to add HTPASSWD_FILE to the rc file, pointing to an
existing, writable, but possibly an initially empty, file, as well as adding
an entry for 'htpasswd' to the COMMANDS hash).
=cut
# usage and sanity checks
usage() if @ARGV and $ARGV[0] eq '-h';
$ENV{GL_USER} or _die "GL_USER not set";
my $htpasswd_file = $rc{HTPASSWD_FILE} || '';
die "htpasswd not enabled\n" unless $htpasswd_file;
die "$htpasswd_file doesn't exist or is not writable\n" unless -w $htpasswd_file;
# prompt
$|++;
print <<EOFhtp;
Please type in your new htpasswd at the prompt. You only have to type it once.
NOTE THAT THE PASSWORD WILL BE ECHOED, so please make sure no one is
shoulder-surfing, and make sure you clear your screen as well as scrollback
history after you're done (or close your terminal instance).
EOFhtp
print "new htpasswd: ";
# get the password and run htpasswd
my $password = <>;
$password =~ s/[\n\r]*$//;
die "empty passwords are not allowed\n" unless $password;
my $res = system("htpasswd", "-mb", $htpasswd_file, $ENV{GL_USER}, $password);
die "htpasswd command seems to have failed with return code: $res.\n" if $res;

View file

@ -1,107 +0,0 @@
#!/usr/bin/perl
use strict;
use warnings;
use Getopt::Long;
use lib $ENV{GL_LIBDIR};
use Gitolite::Rc;
use Gitolite::Common;
use Gitolite::Conf::Load;
=for args
Usage: gitolite info [-lc] [-ld] [<repo name pattern>]
List all existing repos you can access, as well as repo name patterns you can
create repos from (if any).
'-lc' lists creators as an additional field at the end.
'-ld' lists description as an additional field at the end.
The optional pattern is an unanchored regex that will limit the repos
searched, in both cases. It might speed up things a little if you have more
than a few thousand repos.
=cut
# these two are globals
my ( $lc, $ld, $patt ) = args();
print_version();
print_patterns(); # repos he can create for himself
print_phy_repos(); # repos already created
print "\n$rc{SITE_INFO}\n" if $rc{SITE_INFO};
# ----------------------------------------------------------------------
sub args {
my ( $lc, $ld, $patt ) = ( '', '', '' );
my $help = '';
GetOptions(
'lc' => \$lc,
'ld' => \$ld,
'h' => \$help,
) or usage();
usage() if @ARGV > 1 or $help;
$patt = shift @ARGV || '.';
return ( $lc, $ld, $patt );
}
sub print_version {
chomp( my $hn = `hostname -s 2>/dev/null || hostname` );
my $gv = substr( `git --version`, 12 );
$ENV{GL_USER} or _die "GL_USER not set";
print "hello $ENV{GL_USER}, this is " . ($ENV{USER} || "httpd") . "\@$hn running gitolite3 " . version() . " on git $gv\n";
}
sub print_patterns {
my ( $repos, @aa );
# find repo patterns only, call them with ^C flag included
@$repos = grep { !/$REPONAME_PATT/ } @{ lister_dispatch('list-repos')->() };
@aa = qw(R W ^C);
listem( $repos, '', '', @aa );
# but squelch the 'lc' and 'ld' flags for these
}
sub print_phy_repos {
my ( $repos, @aa );
# now get the actual repos and get R or W only
_chdir( $rc{GL_REPO_BASE} );
$repos = list_phy_repos(1);
@aa = qw(R W);
listem( $repos, $lc, $ld, @aa );
}
sub listem {
my ( $repos, $lc, $ld, @aa ) = @_;
my $creator = '';
for my $repo (@$repos) {
next unless $repo =~ /$patt/;
my $perm = '';
$creator = creator($repo) if $lc;
my $desc = '';
for my $d ("$ENV{GL_REPO_BASE}/$repo.git/description") {
next unless $ld and -r $d;
$desc = slurp($d);
chomp($desc);
}
for my $aa (@aa) {
my $ret = access( $repo, $ENV{GL_USER}, $aa, 'any' );
$perm .= ( $ret =~ /DENIED/ ? " " : " $aa" );
}
$perm =~ s/\^//;
next unless $perm =~ /\S/;
print "$perm\t$repo";
print "\t$creator" if $lc;
print "\t$desc" if $ld;
print "\n";
}
}

View file

@ -1,55 +0,0 @@
#!/usr/bin/perl
use strict;
use warnings;
use lib $ENV{GL_LIBDIR};
use Gitolite::Common;
use Gitolite::Conf::Load;
=for usage
Usage: gitolite list-dangling-repos
List all existing repos that no one can access remotely any more. They could
be normal repos that were taken out of "repo" statements in the conf file, or
wildcard repos whose matching "wild" pattern was taken out or changed so it no
longer matches.
I would advise caution if you use this as a basis for deleting repos from the
file system. A bug in this program could cause you to lose important data!
=cut
usage() if @ARGV and $ARGV[0] eq '-h';
# get the two lists we need. %repos is the list of repos in "repo" statements
# in the conf file. %phy_repos is the list of actual repos on disk. Our job
# is to cull %phy_repos of all keys that have a matching key in %repos, where
# "matching" means "string equal" or "regex match".
my %repos = map { chomp; $_ => 1 } `gitolite list-repos`;
for my $r ( grep /^@/, keys %repos ) {
map { chomp; $repos{$_} = 1; } `gitolite list-members $r`;
}
my %phy_repos = map { chomp; $_ => 1 } `gitolite list-phy-repos`;
# Remove exact matches. But for repo names like "gtk+", you could have
# collapsed this into the next step (the regex match).
for my $pr (keys %phy_repos) {
next unless exists $repos{$pr};
delete $repos{$pr};
delete $phy_repos{$pr};
}
# Remove regex matches.
for my $pr (keys %phy_repos) {
my $matched = 0;
my $pr2 = Gitolite::Conf::Load::generic_name($pr);
for my $r (keys %repos) {
if ($pr =~ /^$r$/ or $pr2 =~ /^$r$/) {
$matched = 1;
next;
}
}
delete $phy_repos{$pr} if $matched;
}
# what's left in %phy_repos are dangling repos.
print join("\n", sort keys %phy_repos), "\n";

View file

@ -1,124 +0,0 @@
#!/usr/bin/perl
use strict;
use warnings;
use Getopt::Long;
use lib $ENV{GL_LIBDIR};
use Gitolite::Rc;
use Gitolite::Common;
use Gitolite::Conf::Load;
# gitolite command to lock and unlock (binary) files and deal with locks.
=for usage
Usage: ssh git@host lock -l <repo> <file> # lock a file
ssh git@host lock -u <repo> <file> # unlock a file
ssh git@host lock --break <repo> <file> # break someone else's lock
ssh git@host lock -ls <repo> # list locked files for repo
See doc/locking.mkd for other details.
=cut
usage() if not @ARGV or $ARGV[0] eq '-h';
$ENV{GL_USER} or _die "GL_USER not set";
my $op = '';
$op = 'lock' if $ARGV[0] eq '-l';
$op = 'unlock' if $ARGV[0] eq '-u';
$op = 'break' if $ARGV[0] eq '--break';
$op = 'list' if $ARGV[0] eq '-ls';
usage() if not $op;
shift;
my $repo = shift;
_die "You are not authorised" if access( $repo, $ENV{GL_USER}, 'W', 'any' ) =~ /DENIED/;
_die "You are not authorised" if $op eq 'break' and access( $repo, $ENV{GL_USER}, '+', 'any' ) =~ /DENIED/;
my $file = shift || '';
usage() if $op ne 'list' and not $file;
_chdir( $ENV{GL_REPO_BASE} );
_chdir("$repo.git");
my $ff = "gl-locks";
if ( $op eq 'lock' ) {
f_lock( $repo, $file );
} elsif ( $op eq 'unlock' ) {
f_unlock( $repo, $file );
} elsif ( $op eq 'break' ) {
f_break( $repo, $file );
} elsif ( $op eq 'list' ) {
f_list($repo);
}
# ----------------------------------------------------------------------
# everything below assumes we have already chdir'd to "$repo.git". Also, $ff
# is used as a global.
sub f_lock {
my ( $repo, $file ) = @_;
my %locks = get_locks();
_die "'$file' locked by '$locks{$file}{USER}' since " . localtime( $locks{$file}{TIME} ) if $locks{$file}{USER};
$locks{$file}{USER} = $ENV{GL_USER};
$locks{$file}{TIME} = time;
put_locks(%locks);
}
sub f_unlock {
my ( $repo, $file ) = @_;
my %locks = get_locks();
_die "'$file' not locked by '$ENV{GL_USER}'" if ( $locks{$file}{USER} || '' ) ne $ENV{GL_USER};
delete $locks{$file};
put_locks(%locks);
}
sub f_break {
my ( $repo, $file ) = @_;
my %locks = get_locks();
_die "'$file' was not locked" unless $locks{$file};
push @{ $locks{BREAKS} }, time . " $ENV{GL_USER} $locks{$file}{USER} $locks{$file}{TIME} $file";
delete $locks{$file};
put_locks(%locks);
}
sub f_list {
my $repo = shift;
my %locks = get_locks();
print "\n# locks held:\n\n";
map { print "$locks{$_}{USER}\t$_\t(" . scalar(localtime($locks{$_}{TIME})) . ")\n" } grep { $_ ne 'BREAKS' } sort keys %locks;
print "\n# locks broken:\n\n";
for my $b ( @{ $locks{BREAKS} } ) {
my ( $when, $who, $whose, $how_old, $what ) = split ' ', $b;
print "$who\t$what\t(" . scalar( localtime($when) ) . ")\t(locked by $whose at " . scalar( localtime($how_old) ) . ")\n";
}
}
sub get_locks {
if ( -f $ff ) {
our %locks;
my $t = slurp($ff);
eval $t;
_die "do '$ff' failed with '$@', contact your administrator" if $@;
return %locks;
}
return ();
}
sub put_locks {
my %locks = @_;
use Data::Dumper;
$Data::Dumper::Indent = 1;
$Data::Dumper::Sortkeys = 1;
my $dumped_data = Data::Dumper->Dump( [ \%locks ], [qw(*locks)] );
_print( $ff, $dumped_data );
}

View file

@ -1,77 +0,0 @@
#!/usr/bin/perl
use strict;
use warnings;
my $tid;
BEGIN {
$tid = $ENV{GL_TID} || 0;
delete $ENV{GL_TID};
}
use lib $ENV{GL_LIBDIR};
use Gitolite::Rc;
use Gitolite::Common;
use Gitolite::Conf::Load;
=for usage
Usage 1: gitolite mirror push <slave> <repo>
Usage 2: ssh git@master-server mirror push <slave> <repo>
Forces a push of one repo to one slave.
Usage 1 is directly on the master server. Nothing is checked; if the slave
accepts it, the push happens, even if the slave is not in any slaves
option. This is how you do delayed or lagged pushes to servers that do not
need real-time updates or have bandwidth/connectivity issues.
Usage 2 can be initiated by *any* user who has *any* gitolite access to the
master server, but it checks that the slave is in one of the slaves options
before doing the push.
=cut
usage() if not @ARGV or $ARGV[0] eq '-h';
_die "HOSTNAME not set" if not $rc{HOSTNAME};
my ( $cmd, $host, $repo ) = @ARGV;
usage() if not $repo;
if ( $cmd eq 'push' ) {
valid_slave( $host, $repo ) if exists $ENV{GL_USER};
# will die if host not in slaves for repo
trace( 1, "TID=$tid host=$host repo=$repo", "gitolite mirror push started" );
_chdir( $rc{GL_REPO_BASE} );
_chdir("$repo.git");
if (-f "gl-creator") {
# try to propagate the wild repo, including creator name and gl-perms
my $creator = `cat gl-creator`; chomp($creator);
trace(1, `cat gl-perms 2>/dev/null | ssh $host CREATOR=$creator perms -c \\'$repo\\' 2>/dev/null`);
}
my $errors = 0;
for (`git push --mirror $host:$repo 2>&1`) {
$errors = 1 if $?;
print STDERR "$_" if -t STDERR or exists $ENV{GL_USER};
chomp;
if (/FATAL/) {
$errors = 1;
gl_log( 'mirror', $_ );
} else {
trace( 1, "mirror: $_" );
}
}
exit $errors;
}
sub valid_slave {
my ( $host, $repo ) = @_;
_die "invalid repo '$repo'" unless $repo =~ $REPONAME_PATT;
my $ref = git_config( $repo, "^gitolite-options\\.mirror\\.slaves.*" );
my %list = map { $_ => 1 } map { split } values %$ref;
_die "'$host' not a valid slave for '$repo'" unless $list{$host};
}

View file

@ -1,127 +0,0 @@
#!/usr/bin/perl
use strict;
use warnings;
use lib $ENV{GL_LIBDIR};
use Gitolite::Rc;
use Gitolite::Common;
use Gitolite::Conf::Load;
=for usage
Usage: ssh git@host perms -l <repo>
ssh git@host perms <repo> - <rolename> <username>
ssh git@host perms <repo> + <rolename> <username>
List or set permissions for user-created ("wild") repo. The first usage shown
will list the current contents of the permissions file. The other two will
change permissions, adding or removing a user from a role.
Examples:
ssh git@host perms foo + READERS user1
ssh git@host perms foo + READERS user2
ssh git@host perms foo + READERS user3
----
There is also a batch mode useful for scripting and bulk loading. Do not
combine this with the +/- mode above. This mode also accepts an optional "-c"
flag to create the repo if it does not already exist (assuming $GL_USER has
permissions to create it).
Examples:
cat copy-of-backed-up-gl-perms | ssh git@host perms <repo>
cat copy-of-backed-up-gl-perms | ssh git@host perms -c <repo>
=cut
usage() if not @ARGV or $ARGV[0] eq '-h';
$ENV{GL_USER} or _die "GL_USER not set";
my $list = 0;
if ( $ARGV[0] eq '-l' ) {
$list++;
shift;
getperms(@ARGV); # doesn't return
}
my $generic_error = "repo does not exist, or you are not authorised";
# auto-create the repo if -c passed and repo doesn't exist
if ( $ARGV[0] eq '-c' ) {
shift;
my $repo = $ARGV[0] or usage();
_die "invalid repo '$repo'" unless $repo =~ $REPONAME_PATT;
if (not -d "$rc{GL_REPO_BASE}/$repo.git") {
my $ret = access( $repo, $ENV{GL_USER}, '^C', 'any' );
_die $generic_error if $ret =~ /DENIED/;
require Gitolite::Conf::Store;
Gitolite::Conf::Store->import;
new_wild_repo( $repo, $ENV{GL_USER}, 'perms-c' );
gl_log( 'create', $repo, $ENV{GL_USER}, 'perms-c' );
}
}
my $repo = shift;
setperms(@ARGV);
_system( "gitolite", "trigger", "POST_CREATE", $repo, $ENV{GL_USER}, 'perms' );
# ----------------------------------------------------------------------
sub getperms {
my $repo = shift;
_die $generic_error if repo_missing($repo) or creator($repo) ne $ENV{GL_USER};
my $pf = "$rc{GL_REPO_BASE}/$repo.git/gl-perms";
print slurp($pf) if -f $pf;
exit 0;
}
sub setperms {
_die $generic_error if repo_missing($repo) or creator($repo) ne $ENV{GL_USER};
my $pf = "$rc{GL_REPO_BASE}/$repo.git/gl-perms";
if ( not @_ ) {
# legacy mode; pipe data in
print STDERR "'batch' mode started, waiting for input (run with '-h' for details).\n";
print STDERR "Please hit Ctrl-C if you did not intend to do this.\n";
@ARGV = ();
my @a;
for (<>) {
_die "Invalid role '$1'; check the rc file" if /(\S+)/ and not $rc{ROLES}{$1};
push @a, $_;
}
print STDERR "\n"; # make sure Ctrl-C gets caught
_print( $pf, @a );
return;
}
_die "Invalid syntax. Please re-run with '-h' for detailed usage" if @_ != 3;
my ( $op, $role, $user ) = @_;
_die "Invalid syntax. Please re-run with '-h' for detailed usage" if $op ne '+' and $op ne '-';
_die "Invalid role '$role'; check the rc file" if not $rc{ROLES}{$role};
_die "Invalid user '$user'" if not $user =~ $USERNAME_PATT;
my $text = '';
my @text = slurp($pf) if -f $pf;
my $present = grep { $_ eq "$role $user\n" } @text;
if ( $op eq '-' ) {
if ( not $present ) {
_warn "'$role $user' was not present in file";
} else {
@text = grep { $_ ne "$role $user\n" } @text;
_print( $pf, @text );
}
} else {
if ($present) {
_warn "'$role $user' already present in file";
} else {
push @text, "$role $user\n";
@text = sort @text;
_print( $pf, @text );
}
}
}

View file

@ -1,8 +0,0 @@
#!/usr/bin/perl
use strict;
use warnings;
use lib $ENV{GL_LIBDIR};
use Gitolite::Rc;
print glrc('default-text');

View file

@ -1,5 +0,0 @@
#!/bin/sh
export GL_BYPASS_ACCESS_CHECKS=1
git push "$@"

View file

@ -1,149 +0,0 @@
#!/usr/bin/perl
use strict;
use warnings;
use lib $ENV{GL_LIBDIR};
use Gitolite::Easy;
=for admins
BUNDLE SUPPORT
(1) For each repo in gitolite.conf for which you want bundle support (or
'@all', if you wish), add the following line:
option bundle = 1
Or you can say:
option bundle.ttl = <number>
A bundle file that is more than <number> seconds old (default value
86400, i.e., 1 day) is recreated on the next bundle request. Increase
this if your repo is not terribly active.
Note: a bundle file is also deleted and recreated if it contains a ref
that was then either deleted or rewound in the repo. This is checked
on every invocation.
(2) Add 'rsync' to the COMMANDS list in the rc file
GENERIC RSYNC SUPPORT
TBD
=cut
=for usage
rsync helper for gitolite
BUNDLE SUPPORT
Admins: see src/commands/rsync for setup instructions
Users:
rsync -P git@host:repo.bundle .
# downloads a file called "<basename of repo>.bundle"; repeat as
# needed till the whole thing is downloaded
git clone repo.bundle repo
cd repo
git remote set-url origin git@host:repo
git fetch origin # and maybe git pull, etc. to freshen the clone
GENERIC RSYNC SUPPORT
TBD
=cut
usage() if not @ARGV or $ARGV[0] eq '-h';
# rsync driver program. Several things can be done later, but for now it
# drives just the 'bundle' transfer.
if ( $ENV{SSH_ORIGINAL_COMMAND} =~ /^rsync --server --sender (-[-\w=.]+ )+\. (\S+)\.bundle$/ ) {
my $repo = $2;
$repo =~ s/\.git$//;
# all errors have the same message to avoid leaking info
can_read($repo) or _die "you are not authorised";
my %config = config( $repo, "gitolite-options.bundle" ) or _die "you are not authorised";
my $ttl = $config{'gitolite-options.bundle.ttl'} || 86400; # in seconds (default 1 day)
my $bundle = bundle_create( $repo, $ttl );
$ENV{SSH_ORIGINAL_COMMAND} =~ s( \S+\.bundle)( $bundle);
trace( 1, "rsync bundle", $ENV{SSH_ORIGINAL_COMMAND} );
Gitolite::Common::_system( split ' ', $ENV{SSH_ORIGINAL_COMMAND} );
exit 0;
}
_warn "invalid rsync command '$ENV{SSH_ORIGINAL_COMMAND}'";
usage();
# ----------------------------------------------------------------------
# helpers
# ----------------------------------------------------------------------
sub bundle_create {
my ( $repo, $ttl ) = @_;
my $bundle = "$repo.bundle";
$bundle =~ s(.*/)();
my $recreate = 0;
my ( %b, %r );
if ( -f $bundle ) {
%b = map { chomp; reverse split; } `git ls-remote --heads --tags $bundle`;
%r = map { chomp; reverse split; } `git ls-remote --heads --tags .`;
for my $ref ( sort keys %b ) {
my $mtime = ( stat $bundle )[9];
if ( time() - $mtime > $ttl ) {
trace( 1, "bundle too old" );
$recreate++;
last;
}
if ( not $r{$ref} ) {
trace( 1, "ref '$ref' deleted in repo" );
$recreate++;
last;
}
if ( $r{$ref} eq $b{$ref} ) {
# same on both sides; ignore
delete $r{$ref};
delete $b{$ref};
next;
}
`git rev-list --count --left-right $b{$ref}...$r{$ref}` =~ /^(\d+)\s+(\d+)$/ or _die "git too old";
if ($1) {
trace( 1, "ref '$ref' rewound in repo" );
$recreate++;
last;
}
}
} else {
trace( 1, "no bundle found" );
$recreate++;
}
return $bundle if not $recreate;
trace( 1, "creating bundle for '$repo'" );
-f $bundle and ( unlink $bundle or die "a horrible death" );
system("git bundle create $bundle --branches --tags >&2");
return $bundle;
}
sub trace {
Gitolite::Common::trace(@_);
}

View file

@ -1,192 +0,0 @@
#!/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;
my $help = 0;
GetOptions( 'admin|a=s' => \$admin, 'quiet|q' => \$quiet, 'help|h' => \$help );
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 @_;
}
usage() if $help;
our @pubkeyfiles = @ARGV; @ARGV = ();
my $kd = "$ENV{HOME}/.gitolite/keydir";
if ( not @pubkeyfiles ) {
chomp( @pubkeyfiles = `find $kd -type f -name "*.pub" | sort` );
}
if ( -t STDIN ) {
@ARGV = ("$ENV{HOME}/.ssh/authorized_keys");
}
# ------------------------------------------------------------------------
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 = ( $fp && $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) {
# get the short name for the pubkey file
( my $pkfsn = $pkf ) =~ s(^$kd/)();
my $fp = fprint($pkf);
next unless $fp;
msg 1, "$pkfsn appears to be a COPY of $pkf_by_fp{$fp}\n" if $pkf_by_fp{$fp};
$pkf_by_fp{$fp} ||= $pkfsn;
my $fpu = ( $seen_fprints{$fp}{user} || 'no access' );
msg 0, "$pkfsn 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=.*gitolite-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;
warn "$fp\n" unless $fp =~ /([0-9a-f][0-9a-f](:[0-9a-f][0-9a-f])+)/;
return $1;
}
# ------------------------------------------------------------------------
sub usage {
print <<EOF;
Usage: gitolite sshkeys-lint [-q] [optional list of pubkey filenames]
(optionally, STDIN can be a pipe or redirected from a file; see below)
Look for potential problems in ssh keys.
sshkeys-lint expects:
- the contents of an authorized_keys file via STDIN, otherwise it uses
\$HOME/.ssh/authorized_keys
- one or more pubkey filenames as arguments, otherwise it uses all the keys
found (recursively) in \$HOME/.gitolite/keydir
The '-q' option will print only warnings instead of all mappings.
Note that this runs ssh-keygen -l for each line in the authkeys file and each
pubkey in the argument list, so be wary of running it on something huge. This
is meant for troubleshooting.
EOF
exit 1;
}

View file

@ -1,280 +0,0 @@
#!/usr/bin/perl
use strict;
use warnings;
use lib $ENV{GL_LIBDIR};
use Gitolite::Rc;
use Gitolite::Common;
=for usage
Usage for this command is not that simple. Please read the full documentation
in doc/sskm.mkd or online at http://sitaramc.github.com/gitolite/sskm.html.
=cut
usage() if @ARGV and $ARGV[0] eq '-h';
my $rb = $rc{GL_REPO_BASE};
my $ab = $rc{GL_ADMIN_BASE};
# get to the keydir
_chdir("$ab/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", "$rb/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");
_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("gitolite 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("gitolite 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("gitolite 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("gitolite 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("gitolite 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("gitolite push >/dev/null 2>/dev/null") and die "git push failed\n";
}

View file

@ -1,24 +0,0 @@
#!/bin/sh
# Usage: ssh git@host sudo <user> <command> <arguments>
#
# Let super-user run commands as any other user. "Super-user" is defined as
# "have write access to the gitolite-admin repo".
die() { echo "$@" >&2; exit 1; }
usage() { perl -lne 'print substr($_, 2) if /^# Usage/../^$/' < $0; exit 1; }
[ -z "$2" ] && usage
[ "$1" = "-h" ] && usage
[ -z "$GL_USER" ] && die GL_USER not set
gitolite access -q gitolite-admin $GL_USER W any || die "You are not authorised"
user="$1"; shift
cmd="$1"; shift
# switch user
GL_USER="$user"
# figure out if the command is allowed from a remote user
gitolite query-rc -q COMMANDS $cmd || die "Command '$cmd' not allowed"
gitolite $cmd "$@"

View file

@ -1,17 +0,0 @@
#!/usr/bin/perl
use strict;
use warnings;
use lib $ENV{GL_LIBDIR};
use Gitolite::Rc;
my $svnserve = $rc{SVNSERVE} || '';
$svnserve ||= "/usr/bin/svnserve -r /var/svn/ -t --tunnel-user=%u";
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";

View file

@ -1,31 +0,0 @@
#!/bin/sh
# Usage: ssh git@host symbolic-ref <repo> <arguments to git-symbolic-ref>
#
# allow 'git symbolic-ref' over a gitolite connection
# Security: remember all arguments to commands must match a very conservative
# pattern. Once that is assured, the symbolic-ref command has no security
# related side-effects, so we don't check arguments at all.
# 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.
die() { echo "$@" >&2; exit 1; }
usage() { perl -lne 'print substr($_, 2) if /^# Usage/../^$/' < $0; exit 1; }
[ -z "$1" ] && usage
[ "$1" = "-h" ] && usage
[ -z "$GL_USER" ] && die GL_USER not set
# ----------------------------------------------------------------------
repo=$1; shift
repo=${repo%.git}
gitolite access -q "$repo" $GL_USER W any || die You are not authorised
# change head
cd $GL_REPO_BASE/$repo.git
git symbolic-ref "$@"

View file

@ -1,57 +0,0 @@
#!/usr/bin/perl
use strict;
use warnings;
use lib $ENV{GL_LIBDIR};
use Gitolite::Easy;
=for usage
Usage: ssh git@host who-pushed <repo> <SHA>
Determine who pushed the given commit. The first few hex digits of the SHA
should suffice.
Each line of the output contains the following fields: timestamp, a
transaction ID, username, refname, and the old and new SHAs for the ref.
We assume the logfile names have been left as default, or if changed, in such
a way that they come up oldest first when sorted.
The program searches ALL the log files, in reverse sorted order (i.e., newest
first). This means it could take a long time if your log directory is large
and contains lots of old log files. Patches to limit the search to an
optional date range are welcome.
Note on the "transaction ID" field: if looking at the log file doesn't help
you figure out what its purpose is, please just ignore it.
=cut
usage() if not @ARGV or @ARGV < 2 or $ARGV[0] eq '-h';
usage() if $ARGV[1] !~ /^[0-9a-f]+$/i;
my $repo = shift;
my $sha = shift; $sha =~ tr/A-F/a-f/;
$ENV{GL_USER} and ( can_read($repo) or die "no read permissions on '$repo'" );
# ----------------------------------------------------------------------
my $repodir = "$ENV{GL_REPO_BASE}/$repo.git";
chdir $repodir or die "repo '$repo' missing";
(my $logdir = $ENV{GL_LOGFILE}) =~ s(/[^/]+$)();
for my $logfile ( reverse glob("$logdir/*") ) {
@ARGV = ($logfile);
for my $line ( reverse grep { m(\tupdate\t($repo|$repodir)\t) } <> ) {
chomp($line);
my @fields = split /\t/, $line;
my ($ts, $pid, $who, $ref, $d_old, $new) = @fields[ 0, 1, 4, 6, 7, 8];
# d_old is what you display
my $old = $d_old;
$old = "" if $d_old eq ("0" x 40);
$old = "$old.." if $old;
system("git rev-list $old$new 2>/dev/null | grep ^$sha >/dev/null && echo '$ts $pid $who $ref $d_old $new'");
}
}

View file

@ -1,56 +0,0 @@
#!/usr/bin/perl
use strict;
use warnings;
use lib $ENV{GL_LIBDIR};
use Gitolite::Easy;
=for usage
Usage: gitolite writable <reponame>|@all on|off
Disable/re-enable pushes to all repos or named repo. Useful to run
non-git-aware backups and so on.
'on' enables, 'off' disables, writes (pushes) to the named repo or all repos.
With 'off', any subsequent text is taken to be the message to be shown to
users when their pushes get rejected. If it is not supplied, it will take it
from STDIN; this allows longer messages.
=cut
usage() if not @ARGV or @ARGV < 2 or $ARGV[0] eq '-h';
usage() if $ARGV[1] ne 'on' and $ARGV[1] ne 'off';
my $repo = shift;
my $on = ( shift eq 'on' );
if ( $repo eq '@all' ) {
_die "you are not authorized" if $ENV{GL_USER} and not is_admin();
} else {
_die "you are not authorized" if $ENV{GL_USER} and not owns($repo);
}
my $msg = join( " ", @ARGV );
# try STDIN only if no msg found in args *and* it's an 'off' command
if ( not $msg and not $on ) {
say2 "...please type the message to be shown to users:";
$msg = join( "", <> );
}
my $sf = ".gitolite.down";
my $rb = $ENV{GL_REPO_BASE};
if ( $repo eq '@all' ) {
target( $ENV{HOME} );
} else {
target("$rb/$repo.git");
}
sub target {
my $repodir = shift;
if ($on) {
unlink "$repodir/$sf";
} else {
_print( "$repodir/$sf", $msg );
}
}

View file

@ -1,102 +0,0 @@
#!/usr/bin/perl
# all gitolite CLI tools run as sub-commands of this command
# ----------------------------------------------------------------------
=for args
Usage: gitolite [sub-command] [options]
The following built-in subcommands are available; they should all respond to
'-h' if you want further details on each:
setup 1st run: initial setup; all runs: hook fixups
compile compile gitolite.conf
query-rc get values of rc variables
list-groups list all group names in conf
list-users list all users/user groups in conf
list-repos list all repos/repo groups in conf
list-phy-repos list all repos actually on disk
list-memberships list all groups a name is a member of
list-members list all members of a group
Warnings:
- list-users is disk bound and could take a while on sites with 1000s of repos
- list-memberships does not check if the name is known; unknown names come
back with 2 answers: the name itself and '@all'
In addition, running 'gitolite help' should give you a list of custom commands
available. They may or may not respond to '-h', depending on how they were
written.
=cut
# ----------------------------------------------------------------------
use FindBin;
BEGIN { $ENV{GL_BINDIR} = $FindBin::RealBin; }
BEGIN { $ENV{GL_LIBDIR} = "$ENV{GL_BINDIR}/lib"; }
use lib $ENV{GL_LIBDIR};
use Gitolite::Rc;
use Gitolite::Common;
use strict;
use warnings;
# ----------------------------------------------------------------------
my ( $command, @args ) = @ARGV;
gl_log( 'cli', 'gitolite', @ARGV ) if -d $rc{GL_ADMIN_BASE} and $$ == ( $ENV{GL_TID} || 0 );
args();
# the first two commands need options via @ARGV, as they have their own
# GetOptions calls and older perls don't have 'GetOptionsFromArray'
if ( $command eq 'setup' ) {
shift @ARGV;
require Gitolite::Setup;
Gitolite::Setup->import;
setup();
} elsif ( $command eq 'query-rc' ) {
shift @ARGV;
query_rc(); # doesn't return
# the rest don't need @ARGV per se
} elsif ( $command eq 'compile' ) {
require Gitolite::Conf;
Gitolite::Conf->import;
compile(@args);
} elsif ( $command eq 'trigger' ) {
trigger(@args);
} elsif ( my $c = _which("commands/$command", 'x' ) ) {
trace( 2, "attempting gitolite command $c" );
_system( $c, @args );
exit 0;
} elsif ( $command eq 'list-phy-repos' ) {
_chdir( $rc{GL_REPO_BASE} );
print "$_\n" for ( @{ list_phy_repos(@args) } );
} elsif ( $command =~ /^list-/ ) {
trace( 2, "attempting lister command $command" );
require Gitolite::Conf::Load;
Gitolite::Conf::Load->import;
my $fn = lister_dispatch($command);
print "$_\n" for ( @{ $fn->(@args) } );
} else {
_die "unknown gitolite sub-command";
}
gl_log('END') if $$ == $ENV{GL_TID};
sub args {
usage() if not $command or $command eq '-h';
}
# ----------------------------------------------------------------------

View file

@ -1,239 +0,0 @@
#!/usr/bin/perl
# gitolite shell, invoked from ~/.ssh/authorized_keys
# ----------------------------------------------------------------------
use FindBin;
BEGIN { $ENV{GL_BINDIR} = $FindBin::RealBin; }
BEGIN { $ENV{GL_LIBDIR} = "$ENV{GL_BINDIR}/lib"; }
use lib $ENV{GL_LIBDIR};
# set HOME
BEGIN { $ENV{HOME} = $ENV{GITOLITE_HTTP_HOME} if $ENV{GITOLITE_HTTP_HOME}; }
use Gitolite::Rc;
use Gitolite::Common;
use Gitolite::Conf::Load;
use strict;
use warnings;
# the main() sub expects ssh-ish things; set them up...
my $id = '';
if ( exists $ENV{G3T_USER} ) {
$id = in_file(); # file:// masquerading as ssh:// for easy testing
} elsif ( exists $ENV{SSH_CONNECTION} ) {
$id = in_ssh();
} elsif ( exists $ENV{REQUEST_URI} ) {
$id = in_http();
} else {
_die "who the *heck* are you?";
}
# sanity...
my $soc = $ENV{SSH_ORIGINAL_COMMAND};
$soc =~ s/[\n\r]+/<<newline>>/g;
_die "I don't like newlines in the command: '$soc'\n" if $ENV{SSH_ORIGINAL_COMMAND} ne $soc;
# the INPUT trigger massages @ARGV and $ENV{SSH_ORIGINAL_COMMAND} as needed
trigger('INPUT');
main($id);
gl_log('END') if $$ == $ENV{GL_TID};
exit 0;
# ----------------------------------------------------------------------
sub in_file {
gl_log( 'file', "ARGV=" . join( ",", @ARGV ), "SOC=$ENV{SSH_ORIGINAL_COMMAND}" );
if ( $ENV{SSH_ORIGINAL_COMMAND} =~ /git-\w+-pack/ ) {
print STDERR "TRACE: gsh(", join( ")(", @ARGV ), ")\n";
print STDERR "TRACE: gsh(SOC=$ENV{SSH_ORIGINAL_COMMAND})\n";
}
return 'file';
}
sub in_http {
http_setup_die_handler();
_die "GITOLITE_HTTP_HOME not set" unless $ENV{GITOLITE_HTTP_HOME};
_die "fallback to DAV not supported" 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).
http_simulate_ssh_connection();
$ENV{REMOTE_USER} ||= $rc{HTTP_ANON_USER};
@ARGV = ( $ENV{REMOTE_USER} );
return 'http';
}
sub in_ssh {
my $ip;
( $ip = $ENV{SSH_CONNECTION} || '(no-IP)' ) =~ s/ .*//;
gl_log( 'ssh', "ARGV=" . join( ",", @ARGV ), "SOC=" . ( $ENV{SSH_ORIGINAL_COMMAND} || '' ), "FROM=$ip" );
$ENV{SSH_ORIGINAL_COMMAND} ||= '';
return $ip;
}
# ----------------------------------------------------------------------
# call this once you are sure arg-1 is the username and SSH_ORIGINAL_COMMAND
# has been setup (even if it's not actually coming via ssh).
sub main {
my $id = shift;
umask $rc{UMASK};
# set up the user
my $user = $ENV{GL_USER} = shift @ARGV;
# set up the repo and the attempted access
my ( $verb, $repo ) = parse_soc(); # returns only for git commands
sanity($repo);
$ENV{GL_REPO} = $repo;
my $aa = ( $verb =~ 'upload' ? 'R' : 'W' );
# auto-create?
if ( repo_missing($repo) and access( $repo, $user, '^C', 'any' ) !~ /DENIED/ ) {
require Gitolite::Conf::Store;
Gitolite::Conf::Store->import;
new_wild_repo( $repo, $user, $aa );
gl_log( 'create', $repo, $user, $aa );
}
# a ref of 'any' signifies that this is a pre-git check, where we don't
# yet know the ref that will be eventually pushed (and even that won't
# apply if it's a read operation). See the matching code in access() for
# more information.
unless ( $ENV{GL_BYPASS_ACCESS_CHECKS} ) {
my $ret = access( $repo, $user, $aa, 'any' );
trace( 1, "access($repo, $user, $aa, 'any')", "-> $ret" );
trigger( 'ACCESS_1', $repo, $user, $aa, 'any', $ret );
_die $ret . "\n(or you mis-spelled the reponame)" if $ret =~ /DENIED/;
gl_log( "pre_git", $repo, $user, $aa, 'any', "-> $ret" );
}
trigger( 'PRE_GIT', $repo, $user, $aa, 'any', $verb );
if ($ENV{REQUEST_URI}) {
_system( "git", "http-backend" );
} else {
my $repodir = "'$rc{GL_REPO_BASE}/$repo.git'";
_system( "git", "shell", "-c", "$verb $repodir" );
}
trigger( 'POST_GIT', $repo, $user, $aa, 'any', $verb );
}
# ----------------------------------------------------------------------
sub parse_soc {
my $soc = $ENV{SSH_ORIGINAL_COMMAND};
$soc ||= 'info';
my $git_commands = "git-upload-pack|git-receive-pack|git-upload-archive";
if ( $soc =~ m(^($git_commands) '/?(.*?)(?:\.git(\d)?)?'$) ) {
my ( $verb, $repo, $trace_level ) = ( $1, $2, $3 );
$ENV{D} = $trace_level if $trace_level;
_die "invalid repo name: '$repo'" if $repo !~ $REPONAME_PATT;
trace( 2, "git command", $soc );
return ( $verb, $repo );
}
# after this we should not return; caller expects us to handle it all here
# and exit out
_die "suspicious characters loitering about '$soc'" if $soc !~ $REMOTE_COMMAND_PATT;
my @words = split ' ', $soc;
if ( $rc{COMMANDS}{ $words[0] } ) {
trace( 2, "gitolite command", $soc );
_system( "gitolite", @words );
exit 0;
}
_die "unknown git/gitolite command: '$soc'";
}
sub sanity {
my $repo = shift;
_die "'$repo' contains bad characters" if $repo !~ $REPONAME_PATT;
_die "'$repo' ends with a '/'" if $repo =~ m(/$);
_die "'$repo' contains '..'" if $repo =~ m(\.\.);
}
# ----------------------------------------------------------------------
# helper functions for "in_http"
sub http_setup_die_handler {
$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";
http_print_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 ;-)
}
}
sub http_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;
$args =~ s/%([0-9A-Fa-f]{2})/chr(hex($1))/eg;
$ENV{SSH_ORIGINAL_COMMAND} = $verb;
$ENV{SSH_ORIGINAL_COMMAND} .= " $args" if $args;
http_print_headers(); # in preparation for the eventual output!
}
$ENV{SSH_CONNECTION} = "$ENV{REMOTE_ADDR} $ENV{REMOTE_PORT} $ENV{SERVER_ADDR} $ENV{SERVER_PORT}";
}
my $http_headers_printed = 0;
sub http_print_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 "Content-Type: text/plain\r\n";
print "\r\n";
}

1092
src/gitolite.pm Normal file

File diff suppressed because it is too large Load diff

262
src/gl-auth-command Executable file
View file

@ -0,0 +1,262 @@
#!/usr/bin/perl
use strict;
use warnings;
# ----------------------------------------------------------------------------
# you: what's the invocation?
# me: Hail, O Lord Ganesha, destroyer of obsta...
# you: err hmm not *that* sort of invocation... I meant how does this program
# get invoked?
# me: oh hehe <hides sheepish grin>, ok here we go...
#
# ssh mode
# - started by sshd
# - one 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)
# - special non-gitolite commands: rsync, svnserve, htpasswd
# - 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
# ----------------------------------------------------------------------------
# ----------------------------------------------------------------------------
# common definitions
# ----------------------------------------------------------------------------
# these are set by the "rc" file
our ($GL_LOGT, $GL_CONF_COMPILED, $REPO_BASE, $GIT_PATH, $REPO_UMASK, $GL_ADMINDIR, $RSYNC_BASE, $HTPASSWD_FILE, $GL_WILDREPOS, $GL_WILDREPOS_DEFPERMS, $GL_ADC_PATH, $SVNSERVE, $PROJECTS_LIST, $GL_SLAVE_MODE, $GL_PERFLOGT);
# and these are set by gitolite.pm
our ($R_COMMANDS, $W_COMMANDS, $REPONAME_PATT, $REPOPATT_PATT, $ADC_CMD_ARGS_PATT);
our %repos;
our %groups;
our %repo_config;
# the common setup module is in the same directory as this running program is
my $bindir = $0;
$bindir =~ s/\/[^\/]+$//;
$bindir = "$ENV{PWD}/$bindir" unless $bindir =~ /^\//;
require "$bindir/gitolite.pm";
# ask where the rc file is, get it, and "do" it
&where_is_rc();
die "parse $ENV{GL_RC} failed: " . ($! or $@) unless do $ENV{GL_RC};
# we need to pass GL_ADMINDIR and the bindir to the child hooks
$ENV{GL_ADMINDIR} = $GL_ADMINDIR;
$ENV{GL_BINDIR} = $bindir;
# add a custom path for git binaries, if specified
$ENV{PATH} .= ":$GIT_PATH" if $GIT_PATH;
# set default permission of wildcard repositories
$ENV{GL_WILDREPOS_DEFPERMS} = $GL_WILDREPOS_DEFPERMS if $GL_WILDREPOS_DEFPERMS;
# set the umask before creating any files
umask($REPO_UMASK);
$ENV{GL_REPO_BASE_ABS} = ( $REPO_BASE =~ m(^/) ? $REPO_BASE : "$ENV{HOME}/$REPO_BASE" );
# ----------------------------------------------------------------------------
# start...
# ----------------------------------------------------------------------------
# if the first argument is a "-s", this user is allowed to get a shell using
# this key
my $shell_allowed = 0;
if (@ARGV and $ARGV[0] eq '-s') {
$shell_allowed = 1;
shift;
}
# ----------------------------------------------------------------------------
# set up SSH_ORIGINAL_COMMAND and SSH_CONNECTION in http mode
# ----------------------------------------------------------------------------
# fake out SSH_ORIGINAL_COMMAND and SSH_CONNECTION so the rest of the code
# stays the same (except the exec at the end).
my $user;
if ($ENV{REQUEST_URI}) {
die "fallback to DAV not supported\n" if $ENV{REQUEST_METHOD} eq 'PROPFIND';
# these patterns indicate normal git usage; see "services[]" in
# http-backend.c for how I got that. Also note that "info" is overloaded;
# git uses "info/refs...", while gitolite uses "info" or "info?...". So
# there's a "/" after info in the list below
if ($ENV{PATH_INFO} =~ m(^/(.*)/(HEAD$|info/refs$|objects/|git-(?:upload|receive)-pack$))) {
my $repo = $1;
my $verb = ($ENV{REQUEST_URI} =~ /git-receive-pack/) ? 'git-receive-pack' : 'git-upload-pack';
$ENV{SSH_ORIGINAL_COMMAND} = "$verb '$repo'";
} else {
# this is one of our custom commands; could be anything really,
# because of the adc feature
my ($verb) = ($ENV{PATH_INFO} =~ m(^/(\S+)));
my $args = $ENV{QUERY_STRING};
$args =~ s/\+/ /g;
$ENV{SSH_ORIGINAL_COMMAND} = $verb;
$ENV{SSH_ORIGINAL_COMMAND} .= " $args" if $args;
&print_http_headers(); # in preparation for the eventual output!
}
$ENV{SSH_CONNECTION} = "$ENV{REMOTE_ADDR} $ENV{REMOTE_PORT} $ENV{SERVER_ADDR} $ENV{SERVER_PORT}";
$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;
}
# ----------------------------------------------------------------------------
# logging, timestamp env vars
# ----------------------------------------------------------------------------
$ENV{GL_LOG} = &get_logfilename($GL_LOGT);
# ----------------------------------------------------------------------------
# sanity checks on SSH_ORIGINAL_COMMAND
# ----------------------------------------------------------------------------
# no SSH_ORIGINAL_COMMAND given...
unless ($ENV{SSH_ORIGINAL_COMMAND}) {
# if the user is allowed to use a shell, give him one
if ($shell_allowed) {
my $shell = $ENV{SHELL};
$shell =~ s/.*\//-/; # change "/bin/bash" to "-bash"
&log_it($shell);
exec { $ENV{SHELL} } $shell;
}
# otherwise, pretend he typed in "info" and carry on...
$ENV{SSH_ORIGINAL_COMMAND} = 'info';
}
# ----------------------------------------------------------------------------
# slave mode should not do much
# ----------------------------------------------------------------------------
die "server is in slave mode; you can only fetch\n"
if ($GL_SLAVE_MODE and $ENV{SSH_ORIGINAL_COMMAND} !~ /^(info|expand|get|git-upload-)/);
# ----------------------------------------------------------------------------
# admin defined commands
# ----------------------------------------------------------------------------
# please see doc/admin-defined-commands.mkd for details
if ($GL_ADC_PATH and -d $GL_ADC_PATH) {
my ($cmd, @args) = split ' ', $ENV{SSH_ORIGINAL_COMMAND};
if (-x "$GL_ADC_PATH/$cmd") {
# yes this is rather strict, sorry.
do { die "I don't like $_\n" unless $_ =~ $ADC_CMD_ARGS_PATT } for ($cmd, @args);
&log_it("$GL_ADC_PATH/$ENV{SSH_ORIGINAL_COMMAND}");
exec("$GL_ADC_PATH/$cmd", @args);
}
}
# ----------------------------------------------------------------------------
# get and set perms for actual repo created by wildcard-autoviv
# ----------------------------------------------------------------------------
my $CUSTOM_COMMANDS=qr/^\s*(expand|(get|set)(perms|desc))\b/;
# note that all the subs called here chdir somewhere else and do not come
# back; they all blithely take advantage of the fact that processing custom
# commands is sort of a dead end for normal (git) processing
if ($ENV{SSH_ORIGINAL_COMMAND} =~ $CUSTOM_COMMANDS) {
die "wildrepos disabled, sorry\n" unless $GL_WILDREPOS;
my $cmd = $ENV{SSH_ORIGINAL_COMMAND};
my ($verb, $repo) = ($cmd =~ /^\s*(\S+)(?:\s+'?\/?(.*?)(?:\.git)?'?)?$/);
# deal with "no argument" cases
$verb eq 'expand' ? $repo = '^' : die "$verb needs an argument\n" unless $repo;
if ($repo =~ $REPONAME_PATT and $verb =~ /getperms|setperms/) {
# with an actual reponame, you can "getperms" or "setperms"
get_set_perms($repo, $verb, $user);
}
elsif ($repo =~ $REPONAME_PATT and $verb =~ /(get|set)desc/) {
# with an actual reponame, you can "getdesc" or "setdesc"
get_set_desc($repo, $verb, $user);
}
elsif ($verb eq 'expand') {
# with a wildcard, you can "expand" it to see what repos actually match
die "$repo has invalid characters" unless "x$repo" =~ $REPOPATT_PATT;
expand_wild($GL_ADMINDIR, $GL_CONF_COMPILED, $repo, $user);
} else {
die "$cmd doesn't make sense to me\n";
}
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 ) {
# ok, it's not a normal git command; call the special command helper
&special_cmd ($GL_ADMINDIR, $GL_CONF_COMPILED, $shell_allowed, $RSYNC_BASE, $HTPASSWD_FILE, $SVNSERVE);
exit;
}
die "$repo ends with a slash; I don't like that\n" if $repo =~ /\/$/;
die "$repo has two consecutive periods; I don't like that\n" if $repo =~ /\.\./;
# reponame
$ENV{GL_REPO}=$repo;
# ----------------------------------------------------------------------------
# the real git commands (git-receive-pack, etc...)
# ----------------------------------------------------------------------------
# ----------------------------------------------------------------------------
# first level permissions check
# ----------------------------------------------------------------------------
my ($perm, $creator, $wild) = &repo_rights($repo);
if ($perm =~ /C/) {
# it was missing, and you have create perms
wrap_chdir("$ENV{GL_REPO_BASE_ABS}");
new_repo($repo, "$GL_ADMINDIR/hooks/common", $user);
# note pwd is now the bare "repo.git"; new_repo does that...
wrap_print("gl-perms", "$GL_WILDREPOS_DEFPERMS\n") if $GL_WILDREPOS_DEFPERMS;
&setup_repo_configs($repo, \%repo_config);
&setup_daemon_access($repo);
&add_del_line ("$repo.git", $PROJECTS_LIST, &setup_gitweb_access($repo, '', ''));
wrap_chdir($ENV{HOME});
}
# we know the user and repo; we just need to know what perm he's trying
# aa == attempted access
my $aa = ($verb =~ $R_COMMANDS ? 'R' : 'W');
die "$aa access for $repo DENIED to $user
(Or there may be no repository at the given path. Did you spell it correctly?)\n" unless $perm =~ /$aa/;
# ----------------------------------------------------------------------------
# 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';

578
src/gl-compile-conf Executable file
View file

@ -0,0 +1,578 @@
#!/usr/bin/perl
use strict;
use warnings;
use Data::Dumper;
$Data::Dumper::Indent = 1;
$Data::Dumper::Sortkeys = 1;
# === add-auth-keys ===
# part of the gitolite (GL) suite
# (1) - "compiles" ~/.ssh/authorized_keys from the list of pub-keys
# (2) - also "compiles" the user-friendly GL conf file into something easier
# to parse. We're doing this because both the gl-auth-command and the
# (gl-)update hook need this, and it seems easier to do this than
# replicate the parsing code in both those places. As a bonus, it's
# probably more efficient.
# (3) - finally does what I have resisted doing all along -- handle gitweb and
# git-daemon access. It won't *setup* gitweb/daemon for you -- you have
# to that yourself. What this does is make sure that "repo.git"
# contains the file "git-daemon-export-ok" (for daemon case) and the
# line "repo.git" exists in the "projects.list" file (for gitweb case).
# how run: manual, by GL admin
# when:
# - anytime a pubkey is added/deleted
# - anytime gitolite.conf is changed
# input:
# - GL_CONF (default: ~/.gitolite/conf/gitolite.conf)
# - GL_KEYDIR (default: ~/.gitolite/keydir)
# output:
# - ~/.ssh/authorized_keys (dictated by sshd)
# - GL_CONF_COMPILED (default: ~/.gitolite/conf/gitolite.conf-compiled.pm)
# security:
# - touches a very critical system file that manages the restrictions on
# incoming users. Be sure to audit AUTH_COMMAND and AUTH_OPTIONS (see
# below) on any change to this script
# - no security checks within program. The GL admin runs this manually
# warnings:
# - if the "start" line exists, but the "end" line does not, you lose the
# rest of the existing authkey file. In general, "don't do that (TM)",
# but we do have a "vim -d" popping up so you can see the changes being
# made, just in case...
# ----------------------------------------------------------------------------
# common definitions
# ----------------------------------------------------------------------------
# setup quiet mode if asked; please do not use this when running manually
open STDOUT, ">", "/dev/null" if (@ARGV and shift eq '-q');
# these are set by the "rc" file
our ($GL_ADMINDIR, $GL_CONF, $GL_KEYDIR, $GL_CONF_COMPILED, $REPO_BASE, $REPO_UMASK, $PROJECTS_LIST, $GIT_PATH, $GL_WILDREPOS, $GL_GITCONFIG_KEYS, $GL_GITCONFIG_WILD, $GL_PACKAGE_HOOKS, $GL_BIG_CONFIG, $GL_NO_DAEMON_NO_GITWEB, $GL_NO_CREATE_REPOS, $GL_NO_SETUP_AUTHKEYS, $GL_PERFLOGT);
# and these are set by gitolite.pm
our ($REPONAME_PATT, $REPOPATT_PATT, $USERNAME_PATT, $ABRT, $WARN);
# the common setup module is in the same directory as this running program is
my $bindir = $0;
$bindir =~ s/\/[^\/]+$//;
$bindir = "$ENV{PWD}/$bindir" unless $bindir =~ /^\//;
require "$bindir/gitolite.pm";
# ask where the rc file is, get it, and "do" it
&where_is_rc();
die "$ABRT parse $ENV{GL_RC} failed: " . ($! or $@) unless do $ENV{GL_RC};
# add a custom path for git binaries, if specified
$ENV{PATH} .= ":$GIT_PATH" if $GIT_PATH;
# ----------------------------------------------------------------------------
# definitions specific to this program
# ----------------------------------------------------------------------------
# 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 = ();
# %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 :)
my %repos = ();
# rule sequence number
my $rule_seq = 0;
# <sigh>... 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 = ();
our $current_data_version; # this comes from gitolite.pm
# catch usernames<->pubkeys mismatches; search for "lint" below
my %user_list = ();
# repo configurations
our %repo_config = ();
# gitweb descriptions and owners; plain text, keyed by "$repo.git"
my %desc = ();
my %owner = ();
# set the umask before creating any files
umask($REPO_UMASK);
# ----------------------------------------------------------------------------
# subroutines
# ----------------------------------------------------------------------------
sub expand_list
{
my @list = @_;
my @new_list = ();
for my $item (@list)
{
if ($item =~ /^@/) # 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;
}
# ----------------------------------------------------------------------------
# "compile" GL conf
# ----------------------------------------------------------------------------
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+) = (.*)/)
{
die "$ABRT defining groups is not allowed inside fragments\n"
if $GL_BIG_CONFIG and $fragment ne 'master';
# 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) ) );
die "$ABRT bad group $1\n" unless $1 =~ $REPONAME_PATT;
}
# repo(s)
elsif ($line =~ /^repo (.*)/)
{
# grab the list...
@{ $repos_p } = split ' ', $1;
unless (@{ $repos_p } == 1 and $repos_p->[0] eq '@all') {
# ...expand groups in the default case
@{ $repos_p } = expand_list ( @{ $repos_p } ) unless $GL_BIG_CONFIG;
# ...sanity check
for (@{ $repos_p }) {
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?)) (.* )?= (.+)/)
{
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 { warn "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 or (@users == 1 and $users[0] eq '@all'));
do { die "$ABRT bad username $_\n" unless $_ =~ $USERNAME_PATT } for @users;
s/\bCREAT[EO]R\b/~\$creator/g for @users;
s/\bREADERS\b/\$readers/g for @users;
s/\bWRITERS\b/\$writers/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 ))
{
$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/;
# 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|readers|writers)$/$1 - wild/;
push @{ $repos{$repo}{$p_user} }, [ $rule_seq++, $ref, $perms ]
unless $rurp_seen{$repo}{$p_user}{$ref}{$perms}++;
}
}
}
}
# configuration
elsif ($line =~ /^config (.+) = ?(.*)/)
{
my ($key, $value) = ($1, $2);
my @validkeys = split(' ', ($GL_GITCONFIG_KEYS || '') );
my @matched = grep { $key =~ /^$_$/ } @validkeys;
die "$ABRT git config $key not allowed\n" if (@matched < 1);
for my $repo (@{ $repos_p }) # each repo in the current stanza
{
$repo_config{$repo}{$key} = $value;
# no problem if it's a plain repo (non-pattern, non-groupname)
# OR wild configs are allowed
unless ( ($repo =~ $REPONAME_PATT and $repo !~ /^@/) or $GL_GITCONFIG_WILD) {
my @r = ($repo); # single wildpatt
@r = sort keys %{ $groups{$repo} } if $groups{$repo}; # or a group; get its members
do {
print STDERR "$WARN git config set for $_ but \$GL_GITCONFIG_WILD not set\n" unless $_ =~ $REPONAME_PATT
} for @r;
}
}
}
# include
elsif ($line =~ /^include "(.+)"/)
{
my $file = $1;
$file = "$GL_ADMINDIR/conf/$file" unless $file =~ /^\//;
die "$WARN $fragment attempting to include configuration\n" if $fragment ne 'master';
die "$ABRT included file not found: '$file'\n" unless -f $file;
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 "$WARN $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/;
parse_conf_line( $line, $fragment, \@repos, \%ignored );
}
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');
# parse any delegated fragments
wrap_chdir($GL_ADMINDIR);
for my $fragment_file (glob("conf/fragments/*.conf"))
{
# we already check (elsewhere) that a fragment called "foo" will not try
# to specify access control for a repo whose name is not "foo" or is not
# part of a group called "foo" created by master
# meanwhile, I found a possible attack where the admin for group B creates
# a "convenience" group of (a subset of) his users, and then the admin for
# repo group A (alphabetically before B) adds himself to that same group
# in his own fragment.
# as a result, admin_A now has access to group B repos :(
# so now we lock the groups hash to the value it had after parsing
# "master", and localise any changes to it by this fragment so that they
# don't propagate to the next fragment. Thus, each fragment now has only
# those groups that are defined in "master" and itself
local %groups = %groups;
my $fragment = $fragment_file;
$fragment =~ s/^conf\/fragments\/(.*).conf$/$1/;
parse_conf_file($fragment_file, $fragment);
}
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([\%repo_config], [qw(*repo_config)]) if %repo_config;
# the dump uses single quotes, but we convert any strings containing $creator,
# $readers, $writers, to double quoted strings. A wee bit sneaky, but not too
# much...
$dumped_data =~ s/'(?=[^']*\$(?:creator|readers|writers|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|readers|writers|gl_user))~?(.*?)'/"$1"/g;
print $compiled_fh $dumped_data;
}
close $compiled_fh or die "$ABRT close compiled-conf failed: $!\n";
rename "$GL_CONF_COMPILED.new", "$GL_CONF_COMPILED";
# ----------------------------------------------------------------------------
# (that ends the config file compiler and write)
# ----------------------------------------------------------------------------
# ----------------------------------------------------------------------------
# what's the git version?
# ----------------------------------------------------------------------------
# we don't like stuff older than 1.6.2
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.2\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 < 10602; # that's 1.6.2 to you
# ----------------------------------------------------------------------------
# the rest of this program can be "switched off"; see doc/big-config.mkd for
# details.
# ----------------------------------------------------------------------------
# ----------------------------------------------------------------------------
# any new repos to be created?
# ----------------------------------------------------------------------------
# repo-base needs to be an absolute path for this loop to work right
# so if it was not already absolute, prefix $HOME.
$ENV{GL_REPO_BASE_ABS} = ( $REPO_BASE =~ m(^/) ? $REPO_BASE : "$ENV{HOME}/$REPO_BASE" );
unless ($GL_NO_CREATE_REPOS) {
wrap_chdir("$ENV{GL_REPO_BASE_ABS}");
# autocreate repos. 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;
next if $repo =~ m(^\@|EXTCMD/); # these are not real 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("$ENV{GL_REPO_BASE_ABS}");
}
# when repos are copied over from elsewhere, one had to run easy install
# once again to make the new (OS-copied) repo contain the proper update
# hook. Perhaps we can make this easier now, and eliminate the easy
# install, with a quick check (and a new, empty, "hook" as a sentinel)
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;
}
}
}
# ----------------------------------------------------------------------------
# collect repo_patt for each actual repo
# ----------------------------------------------------------------------------
# go through each actual repo on disk, and match it to either its own name in
# the config (non-wild) or a wild pattern that matches it. Lots of things
# later will need this correspondence so we may as well snarf it in one shot
my %repo_patts = ();
%repo_patts = &collect_repo_patts(\%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
# ----------------------------------------------------------------------------
# all these require a "chdir" to the repo, so we club them for efficiency
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 (keys %repo_patts) {
wrap_chdir("$ENV{GL_REPO_BASE_ABS}/$repo.git");
# daemon is easy
&setup_daemon_access($repo);
}
for my $repo (keys %repo_patts) {
wrap_chdir("$ENV{GL_REPO_BASE_ABS}/$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
# conf/example.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 repo_config hash, which, (phew!) is needed for a match
# that eventually gets you a valid $repo_config{} below
&setup_repo_configs($repo, \%repo_config) if $repo_config{$repo};
}
# write out the project list
my $projlist_fh = wrap_open( ">", $PROJECTS_LIST);
for my $proj (sort keys %projlist) {
print $projlist_fh "$proj\n";
}
close $projlist_fh;
# ----------------------------------------------------------------------------
# "compile" ssh authorized_keys
# ----------------------------------------------------------------------------
unless ($GL_NO_SETUP_AUTHKEYS) {
&setup_authkeys($bindir, $GL_KEYDIR, \%user_list);
}

101
src/gl-conf-convert Executable file
View file

@ -0,0 +1,101 @@
#!/usr/bin/perl -w
use strict;
use warnings;
# migrate gitosis.conf to gitolite.conf format
# not very smart, but there shouldn't be any errors for simple configurations.
# the biggest thing you'll find is probably some comments rearranged or
# something, due to the "flush" thing below
# for stuff it can't handle, it'll ignore the trivial ones (like gitweb and
# daemon), and put in an obviously syntax error-ed line for "repositories" and
# "map" statements.
my @repos;
my @RO_repos;
my @comments;
my @users;
my $groupname;
# a gitosis.conf stanza ends when a new "[group name]" line shows up, so you
# can't write as you go; you have to accumulate and flush
sub flush {
die "repos but no users?\n" if (not @users and (@repos or @RO_repos));
# just a groupname
if (@users and not (@repos or @RO_repos)) {
print "\@$groupname = ", join(" ", @users), "\n";
}
# RW repos
if (@repos)
{
print "repo ", join(" ", @repos), "\n";
print " RW = ", join(" ", @users), "\n";
}
# RO repos
if (@RO_repos)
{
print "repo ", join(" ", @RO_repos), "\n";
print " R = ", join(" ", @users), "\n";
}
# comments; yes there'll be some reordering, sorry!
print @comments if @comments;
# empty out for next round
@users = ();
@repos = ();
@RO_repos = ();
@comments = ();
}
while (<>)
{
# pure comment lines or blank lines
if (/^\s*#/ or /^\s*$/) {
push @comments, $_;
next;
}
# not supported
if (/^repositories *=/ or /^map /) {
print STDERR "not supported: $_";
s/^/NOT SUPPORTED: /;
print;
next;
}
chomp;
# normalise whitespace to help later regexes
s/\s+/ /g;
s/ ?= ?/ = /;
s/^ //;
s/ $//;
# the chaff...
next if /^\[(gitosis|repo)\]$/
or /^(gitweb|daemon|loglevel|description|owner) =/;
# the wheat...
if (/^members = (.*)/) {
push @users, split(' ', $1);
next;
}
if (/^write?able = (.*)/) {
push @repos, split(' ', $1);
next;
}
if (/^readonly = (.*)/) {
push @RO_repos, split(' ', $1);
next;
}
# new group starts
if (/^\[group (.*?) ?\]/) {
flush();
$groupname = $1;
}
}
flush();

104
src/gl-dont-panic Executable file
View file

@ -0,0 +1,104 @@
#!/bin/sh
usage() {
cat <<EOF
First: DON'T PANIC
NOTE: This advice pertains to gitolite specific issues. If you don't
have ANY access to the server at all, it is OK to panic.
Step 1: prepare
- copy this program to your gitolite server
- if you lost your admin key, create a new keypair on your workstation
and copy the pub part of this new key also to the server
- rename it to whatever your gitolite admin username is, with a .pub
extension. (Example, I would call it "sitaram.pub")
Step 2: use one of the fixes below (on the server)
- (FIX #1: REWINDING BAD ADMIN COMMITS) if your last commit(s) to the
gitolite-admin repo pushed a very bad config and you want to rewind it
to a known good state, run this:
$0 rewind
(this doesn't actually rewind; it creates a new commit that has
the same state as the last good commit, which has the same effect)
- (FIX #2: PUSHING A NEW ADMIN KEY) if you lost your admin key, or you
had used the wrong key initially, then you get yourself a new keypair
and run this with the new pubkey:
$0 sitaram.pub # example using my name
Please note that this simply *replaces* the key for user "sitaram".
It does NOT add a new admin called "sitaram". In fact it does not
touch the config file (access rules) at all.
Step 3: completing the fix (on your workstation)
- do a 'git pull' on the gitolite admin clone or make a fresh clone
EOF
exit 1
}
if [ -z "$1" ]
then
usage
fi
# ------------------------------------------------------------------------
# arg check
die() { echo "$@"; exit 1; }
cd $HOME # if he didn't *start* there, it's his bloody fault
[ -f "$1" ] || [ "$1" = "rewind" ] || die "need a valid file or 'rewind'"
if [ "$1" = "rewind" ]
then
:
else
bn1=`basename $1`;
admin_name=`basename $1 .pub`;
[ "$bn1" = "$admin_name" ] && die "filename needs to end in '.pub'"
fi
# ------------------------------------------------------------------------
# setup stuff
REPO_BASE=$( cd $HOME; perl -e 'do ".gitolite.rc"; print $REPO_BASE' )
GL_BINDIR=$( cd $HOME; perl -ne 'print($1), exit if /^command="(.*?)\/gl-auth-command /' < $HOME/.ssh/authorized_keys)
GL_ADMINDIR=$(cd $HOME; perl -e 'do ".gitolite.rc"; print $GL_ADMINDIR')
export REPO_BASE
export GL_BINDIR
export GL_ADMINDIR
TEMPDIR=$(mktemp -d)
export TEMPDIR
trap "/bin/rm -rf $TEMPDIR" 0
# ------------------------------------------------------------------------
# rewind the admin repo
if [ "$1" = "rewind" ]
then
git clone $REPO_BASE/gitolite-admin.git $TEMPDIR
cd $TEMPDIR
echo printing the last 9 commits to the config; echo
git log -9 --date=relative --format="%h %ar%x09%s" | perl -pe 'print "$.\t"'
echo; read -p 'please enter how many commits you want to rewind: ' n
good=`git rev-parse --short HEAD~$n`
git checkout -f $good .
git commit -m "emergency revert to $good"
GL_BYPASS_UPDATE_HOOK=1 git push
exit $?
fi
# ------------------------------------------------------------------------
# add/overwrite a key ($1)
git clone $REPO_BASE/gitolite-admin.git $TEMPDIR
cp $1 $TEMPDIR/keydir
cd $TEMPDIR
git add keydir
git commit -m "emergency add/update $admin_name key (from $1)"
GL_BYPASS_UPDATE_HOOK=1 git push
exit $?

648
src/gl-easy-install Executable file
View file

@ -0,0 +1,648 @@
#!/bin/bash
# easy install for gitolite
# you run this on the client side, and it takes care of all the server side
# work. You don't have to do anything on the server side directly
# to do a manual install (since I have tested this only on Linux), open this
# script in a nice, syntax coloring, text editor and follow the instructions
# prefixed by the word "MANUAL" in the comments below :-)
# run without any arguments for "usage" info
# important setting: bail on any errors (else we have to check every single
# command!)
set -e
# ----------------------------------------------------------------------
# bootstrap and main
# ----------------------------------------------------------------------
if [[ $1 != boot/strap ]]
then
# did someone tell you you can't call functions before they're defined in
# bash? Don't believe everything you hear ;-)
. $0 boot/strap
main "$@"
cleanup
exit 0
fi
# ----------------------------------------------------------------------
# no direct executable statements after this; only functions
# ----------------------------------------------------------------------
main() {
basic_sanity "$@"
setup_tempdir
version_info "$@"
[[ -n $admin_name ]] && setup_local_ssh
copy_gl # src, conf, etc
run_install
[[ $upgrade == 0 ]] && initial_conf_key
# MANUAL: cd to $GL_ADMINDIR and run "src/gl-compile-conf"
ssh $p_port $user@$host "cd $GL_ADMINDIR; \$PWD/src/gl-compile-conf $quiet"
setup_pta
clone_it
}
# ----------------------------------------------------------------------
# setup temp files
# ----------------------------------------------------------------------
setup_tempdir() {
export tmpgli=tmp-gl-install
trap cleanup 0
mkdir -p $tmpgli
}
cleanup() {
rm -rf $tmpgli
}
# ----------------------------------------------------------------------
# service functions
# ----------------------------------------------------------------------
die() { echo "$@"; echo; echo "run $0 without any arguments for help and tips"; cleanup; exit 1; }
prompt() {
# receives two arguments. A short piece of text to be displayed, without
# pausing, in "quiet" mode, and a much longer one to be displayed, *with*
# a pause, in normal (verbose) mode
[[ $quiet == -q ]] && [[ -n $1 ]] && {
eval "echo \"$1\""
return
}
shift
echo
echo
echo ------------------------------------------------------------------------
eval "echo \"$1\""
echo
read -p '...press enter to continue or Ctrl-C to bail out'
}
usage() {
cat <<EOFU
Usage: $0 [-q] user host [port] admin_name # install
$0 [-q] user host [port] # upgrade
- (optional) "-q" as first arg sets "quiet" mode: no verbose descriptions of
what is going on, no pauses unless absolutely necessary
- "user" is the username on the server where you will be installing gitolite
- "host" is that server's hostname (or IP address)
- "port" is the ssh server port on "host"; optional, defaults to 22
- "admin_name" is *your* name as it should appear in the eventual gitolite
config file. For upgrades (ie., gitolite is already installed on the
server), this argument is not needed, and will be *ignored* if provided.
- (optional) "host_nickname" is a nickname that can be given to a particular
installation of gitolite. This allows for multiple servers to easily be
administered from this machine. Defaults to "gitolite"
Example usage: $0 git my.git.server sitaram [gitolite_server_1]
Notes:
- "user","admin_name" and "host_nickname" must be simple names -- no
special characters etc please (only alphanumerics, dot, hyphen,
underscore)
- traditionally, the "user" is "git", but it can be anything you want
- "admin_name" should be your name, for clarity, or whoever will be the
gitolite admin
Pre-requisites:
- you must already have pubkey based access to user@host. If you currently
only have password access, use "ssh-copy-id" or something equivalent (or
copy the key manually). Somehow (doesn't matter how), get to the point
where you can type "ssh user@host" and get a command line.
**DO NOT RUN THIS PROGRAM UNTIL THAT WORKS**
EOFU
exit 1;
}
# ----------------------------------------------------------------------
# basic sanity / argument checks
# ----------------------------------------------------------------------
basic_sanity() {
# MANUAL: this *must* be run as "src/gl-easy-install", not by cd-ing to
# src and then running "./gl-easy-install"
bindir=${0%/*}
# switch to parent of bindir; we assume the conf files are all there
cd "$bindir"; cd ..
# are we in quiet mode?
quiet=
[[ "$1" == "-q" ]] && {
quiet=-q
shift
}
# MANUAL: (info) we'll use "git" as the user, "server" as the host, and
# "sitaram" as the admin_name in example commands shown below, if any
[[ -z $2 ]] && usage
user=$1
host=$2
port=22
admin_name=$3
host_nickname=$4
if [ -z $4 ]
then
host_nickname="gitolite"
fi
# but if the 3rd arg is a number, that's a port number, and the 4th arg is
# the admin_name
if echo $3 | perl -lne 'exit 1 unless /^[0-9]+$/'
then
port=$3
admin_name=$4
host_nickname=$5
if [ -z $5 ]
then
host_nickname=gitolite
fi
fi
echo $user | perl -lne 'exit 1 if /[^a-zA-Z0-9._-]/' ||
die "user '$user' invalid"
[[ "$user" == "root" ]] && die I refuse to install to root
echo $admin_name | perl -lne 'exit 1 if /[^a-zA-Z0-9._-]/' ||
die "admin_name '$admin_name' invalid"
echo $host_nickname | perl -lne 'exit 1 if /[^a-zA-Z0-9._-]/' ||
die "host nickname '$host_nickname' invalid"
# MANUAL: make sure you're in the gitolite directory, at the top level.
# The following files should all be visible:
ls hooks/gitolite-admin/post-update \
hooks/common/update \
src/gitolite.pm \
src/gl-install \
src/gl-auth-command \
src/gl-compile-conf \
conf/example.conf \
conf/example.gitolite.rc >/dev/null ||
die "cant find at least some files in gitolite sources/config; aborting"
# MANUAL: make sure you have password-less (pubkey) auth on the server.
# That is, running "ssh git@server" should log in straight away, without
# asking for a password
[[ $port -ne 22 ]] && p_port="-p $port"
ssh $p_port -o PasswordAuthentication=no $user@$host true ||
die "pubkey access didn't work; please set it up using 'ssh-copy-id' or something"
}
# ----------------------------------------------------------------------
# version info
# ----------------------------------------------------------------------
version_info() {
# MANUAL: if needed, make a note of the version you are upgrading from, and to
# 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 HEAD 2>/dev/null > conf/VERSION || echo '(unknown)' > conf/VERSION
else
[[ -f conf/VERSION ]] || echo '(unknown)' > conf/VERSION
fi
# what was the old version there?
export upgrade_details="you are upgrading \
$(ssh $p_port $user@$host cat gitolite-install/conf/VERSION 2>/dev/null || echo '(or installing first-time)' ) \
to $(cat conf/VERSION)"
prompt "$upgrade_details" "$v_upgrade_details"
}
# ----------------------------------------------------------------------
# new keypair, ssh-config para; only on "install" (not upgrade)
# ----------------------------------------------------------------------
setup_local_ssh() {
# MANUAL: create a new key for you as a "gitolite user" (as opposed to you
# as the "gitolite admin" who needs to login to the server and get a
# command line). For example, "ssh-keygen -t rsa ~/.ssh/sitaram"; this
# would create two files in ~/.ssh (sitaram and sitaram.pub)
prompt "setting up keypair..." "$v_setting_up_keypair"
if [[ -f "$HOME/.ssh/$admin_name.pub" ]]
then
prompt "" "$v_reuse_pubkey"
else
ssh-keygen -t rsa -f "$HOME/.ssh/$admin_name" || die "ssh-keygen failed for some reason..."
fi
# MANUAL: copy the pubkey created to the server, say to /tmp. This would
# be "scp ~/.ssh/sitaram.pub git@server:/tmp" (the script does this at a
# later stage, you do it now for convenience). Note: only the pubkey
# (sitaram.pub). Do NOT copy the ~/.ssh/sitaram file -- that is a private
# key!
# MANUAL: if you're running ssh-agent (see if you have an environment
# variable called SSH_AGENT_PID in your "env"), you should add this new
# key. The command is "ssh-add ~/.ssh/sitaram"
if ssh-add -l &>/dev/null
then
prompt " ...adding key to agent..." "$v_ssh_add"
ssh-add "$HOME/.ssh/$admin_name"
fi
# MANUAL: you now need to add some lines to the end of your ~/.ssh/config
# file. If the file doesn't exist, create it. Make sure the file is
# "chmod 644".
# The lines to be included look like this:
# host gitolite
# user git
# hostname server
# port 22
# identityfile ~/.ssh/sitaram
echo "host $host_nickname
user $user
hostname $host
port $port
identityfile ~/.ssh/$admin_name" > $tmpgli/.gl-stanza
if grep "host *$host_nickname" "$HOME/.ssh/config" &>/dev/null
then
prompt "found gitolite para in ~/.ssh/config; assuming it is correct..." "$v_found_para"
else
prompt "creating gitolite para in ~/.ssh/config..." "$v_creating_para"
cat $tmpgli/.gl-stanza >> "$HOME/.ssh/config"
# if the file didn't exist at all, it might have the wrong permissions
chmod 644 "$HOME/.ssh/config"
fi
}
# ----------------------------------------------------------------------
# server side
# ----------------------------------------------------------------------
copy_gl() {
# MANUAL: copy the gitolite directories "src", "conf", and "doc" to the
# server, to a directory called (for example) "gitolite-install". You may
# have to create the directory first.
ssh $p_port $user@$host mkdir -p gitolite-install
scp $quiet ${p_port/p/P} -p -r src conf doc hooks $user@$host:gitolite-install/
# MANUAL: now log on to the server (ssh git@server) and get a command
# line. This step is for your convenience; the script does it all from
# the client side but that may be too much typing for manual use ;-)
# MANUAL: cd to the "gitolite-install" directory where the sources are.
# Then copy conf/example.gitolite.rc as ~/.gitolite.rc and edit it if you
# wish to change any paths. Make a note of the GL_ADMINDIR and REPO_BASE
# paths; you will need them later
prompt "finding/creating gitolite rc..." "$v_edit_glrc"
# lets try and get the file from there first
if scp ${p_port/p/P} $user@$host:.gitolite.rc $tmpgli &>/dev/null
then
prompt " ...trying to reuse existing rc" \
"Oh hey... you already had a '.gitolite.rc' file on the server.
Let's see if we can use that instead of the default one..."
< $tmpgli/.gitolite.rc perl -ne 'print "$1\n" if /^\s*(\$\w+) *=/' | sort > $tmpgli/glrc.old
< conf/example.gitolite.rc perl -ne 'print "$1\n" if /^\s*(\$\w+) *=/' | sort > $tmpgli/glrc.new
# msysgit doesn't have "comm". diff is not ideal for our purposes
# because we only care about differences in one direction, but we'll
# have to make do...
set +e
diff -u $tmpgli/glrc.old $tmpgli/glrc.new | grep '^+.*\$' > $tmpgli/glrc.comm13
set -e
if [[ ! -s $tmpgli/glrc.comm13 ]]
then
[[ $quiet == -q ]] || ${VISUAL:-${EDITOR:-vi}} $tmpgli/.gitolite.rc
else
echo new variables found in rc file:
cat $tmpgli/glrc.comm13
echo
# MANUAL: if you're upgrading, read the instructions below and
# manually make sure your final ~/.gitolite.rc has both your existing
# customisations as well as any new variables that the new version of
# gitolite has introduced
prompt "" "$v_upgrade_glrc"
${VISUAL:-${EDITOR:-vi}} conf/example.gitolite.rc $tmpgli/.gitolite.rc
fi
else
cp conf/example.gitolite.rc $tmpgli/.gitolite.rc
[[ $quiet == -q ]] || ${VISUAL:-${EDITOR:-vi}} $tmpgli/.gitolite.rc
fi
# copy the rc across
scp $quiet ${p_port/p/P} $tmpgli/.gitolite.rc $user@$host:
}
run_install() {
prompt "installing/upgrading..." "$v_ignore_stuff"
# extract the GL_ADMINDIR, REPO_BASE and GIT_PATH locations
GL_ADMINDIR=$(ssh $p_port $user@$host "perl -e 'do \".gitolite.rc\"; print \$GL_ADMINDIR'")
REPO_BASE=$( ssh $p_port $user@$host "perl -e 'do \".gitolite.rc\"; print \$REPO_BASE'")
GIT_PATH=$( ssh $p_port $user@$host "perl -e 'do \".gitolite.rc\"; print \$GIT_PATH'")
# determine if this is an upgrade; we decide based on whether a file
# called $GL_ADMINDIR/conf/gitolite.conf exists on the remote side. We
# can't do this till we know the correct value for GL_ADMINDIR
upgrade=0
if ssh $p_port $user@$host cat $GL_ADMINDIR/conf/gitolite.conf &> /dev/null
then
upgrade=1
ssh $p_port $user@$host cat $GL_ADMINDIR/conf/gitolite.conf 2> /dev/null | grep '@SHELL' &&
prompt "" "$v_at_shell_bwi"
[[ -n $admin_name ]] && echo -e "\n *** WARNING ***: looks like an upgrade... ignoring argument '$admin_name'"
else
[[ -z $admin_name ]] && die " *** ERROR ***: doesn't look like an upgrade, so I need a name for the admin"
fi
# MANUAL: still in the "gitolite-install" directory? Good. Run
# "src/gl-install"
ssh $p_port $user@$host "cd gitolite-install; src/gl-install $quiet"
# MANUAL: if you're upgrading, run "src/gl-compile-conf" and you're done!
# -- ignore the rest of this file for the purposes of an upgrade
}
# ----------------------------------------------------------------------
# from here on it's install only
# ----------------------------------------------------------------------
# MANUAL: setup the initial config file. Edit $GL_ADMINDIR/conf/gitolite.conf
# and add at least the following lines to it:
# repo gitolite-admin
# RW+ = sitaram
initial_conf_key() {
echo "#gitolite conf
# please see conf/example.conf for details on syntax and features
repo gitolite-admin
RW+ = $admin_name
repo testing
RW+ = @all
" > $tmpgli/gitolite.conf
# send the config and the key to the remote
scp $quiet ${p_port/p/P} $tmpgli/gitolite.conf $user@$host:$GL_ADMINDIR/conf/
scp $quiet ${p_port/p/P} "$HOME/.ssh/$admin_name.pub" $user@$host:$GL_ADMINDIR/keydir
}
# ----------------------------------------------------------------------
# hey lets go the whole hog on this; setup push-to-admin!
# ----------------------------------------------------------------------
setup_pta() {
# MANUAL: you have to now make the first commit in the admin repo. This
# is a little more complex, so read carefully and substitute the correct
# paths. What you have to do is:
# cd $REPO_BASE/gitolite-admin.git
# GIT_WORK_TREE=$GL_ADMINDIR git add conf/gitolite.conf keydir
# GIT_WORK_TREE=$GL_ADMINDIR git commit -am start
# Substitute $GL_ADMINDIR and $REPO_BASE appropriately. Note there is no
# space around the "=" in the second and third lines.
echo "cd $REPO_BASE/gitolite-admin.git
PATH=\$PATH:$GIT_PATH
GIT_WORK_TREE=$GL_ADMINDIR git add conf/gitolite.conf keydir
GIT_WORK_TREE=$GL_ADMINDIR git diff --cached --quiet 2>/dev/null || GIT_WORK_TREE=$GL_ADMINDIR git commit -am start
" | ssh $p_port $user@$host
# MANUAL: now that the admin repo is created, you have to set the hooks
# properly. The install program does this. So cd back to the
# "gitolite-install" directory and run "src/gl-install"
ssh $p_port $user@$host "cd gitolite-install; src/gl-install $quiet"
# MANUAL: you're done! Log out of the server, come back to your
# workstation, and clone the admin repo using "git clone
# gitolite:gitolite-admin", or pull once again if you already have a
# clone
}
clone_it()
{
cleanup
cd "$HOME"
if [[ -d $host_nickname-admin ]]
then
echo $HOME/$host_nickname-admin exists, skipping clone step...
else
prompt "cloning $host_nickname-admin repo..." "$v_cloning"
git clone $host_nickname:gitolite-admin $host_nickname-admin
fi
# MANUAL: be sure to read the message below; this applies to you too...
echo
echo
echo ---------------------------------------------------------------
eval "echo \"$v_done\""
}
# ----------------------------------------------------------------------
# prompt strings
# ----------------------------------------------------------------------
v_upgrade_details="
\$upgrade_details
Note: getting '(unknown)' for the 'from' version should only happen once.
Getting '(unknown)' for the 'to' version means you are probably installing
from a tar file dump, not a real clone. This is not an error but it's nice to
have those version numbers in case you need support. Try and install from a
clone
"
v_setting_up_keypair="
the next command will create a new keypair for your gitolite access
The pubkey will be \$HOME/.ssh/\$admin_name.pub. You will have to choose a
passphrase or hit enter for none. I recommend not having a passphrase for
now, *especially* if you do not have a passphrase for the key which you are
already using to get server access!
Add one using 'ssh-keygen -p' after all the setup is done and you've
successfully cloned and pushed the gitolite-admin repo. After that, install
'keychain' or something similar, and add the following command to your bashrc
(since this is a non-default key)
ssh-add "\\\$HOME/.ssh/\$admin_name"
This makes using passphrases very convenient.
"
v_reuse_pubkey="
Hmmm... pubkey \$HOME/.ssh/\$admin_name.pub exists; should I just (re-)use it?
IMPORTANT: once the install completes, *this* key can no longer be used to get
a command line on the server -- it will be used by gitolite, for git access
only. If that is a problem, please ABORT now.
doc/6-ssh-troubleshooting.mkd will explain what is happening here, if you need
more info.
"
v_ssh_add="
you're running ssh-agent. We'll try and do an ssh-add of the
private key we just created, otherwise this key won't get picked up. If
you specified a passphrase in the previous step, you'll get asked for one
now -- type in the same one.
"
v_found_para="
your \\\$HOME/.ssh/config already has settings for gitolite. I will assume
they're correct, but if they're not, please edit that file, delete that
paragraph (that line and the following few lines), Ctrl-C, and rerun.
In case you want to check right now (from another terminal) if they're
correct, here's what they are *supposed* to look like:
\$(cat \$tmpgli/.gl-stanza)
"
v_creating_para="
creating settings for your gitolite access in \$HOME/.ssh/config;
these are the lines that will be appended to your ~/.ssh/config:
\$(cat \$tmpgli/.gl-stanza)
"
v_edit_glrc="
the gitolite rc file needs to be edited by hand. The defaults are sensible,
so if you wish, you can just exit the editor.
Otherwise, make any changes you wish and save it. Read the comments to
understand what is what -- the rc file's documentation is inline.
Please remember this file will actually be copied to the server, and that all
the paths etc. represent paths on the server!
"
v_upgrade_glrc="
looks like you're upgrading, and there are some new rc variables that this
version is expecting that your old rc file doesn't have.
I'm going to run your \\\$EDITOR with two filenames. The first is the example
file from this gitolite version. It will have a block (code and comments) for
each of the variables shown above with a '+' sign.
The second is your current rc file, the destination. Copy those lines into
this file, preferably *with* the surrounding comments (for clarity) and save
it.
This is necessary; please dont skip this!
[It's upto you to figure out how your \\\$EDITOR handles 2 filename arguments,
switch between them, copy lines, etc ;-)]
"
v_ignore_stuff="
ignore any 'please edit this file' or 'run this command' type lines in the
next set of command outputs coming up. They're only relevant for a manual
install, not this one...
"
v_at_shell_bwi="
you are using the @SHELL feature in your gitolite config. This feature has
now changed in a backward incompatible way; see doc/6-ssh-troubleshooting.mkd
for information on migrating this to the new syntax.
DO NOT hit enter unless you have understood that information and properly
migrated your setup, or you are sure you have shell access to the server
through some other means than the $admin_name key.
"
v_done="
done!
IMPORTANT NOTE -- PLEASE READ!!!
*Your* URL for cloning any repo from this server will be
\$host_nickname:reponame.git
Note: If you are upgrading and you set a host nickname during initial
setup, please use that host nickname instead of \"gitolite\"
above.
*Other* users you set up will have to use
\$user@\$host:reponame.git
However, if your server uses a non-standard ssh port, they should use
ssh://\$user@\$host:\$port/reponame.git
If this is your first time installing gitolite, please also:
tail -31 \$0
for next steps.
"
v_cloning="
now we will clone the gitolite-admin repo to your workstation and see if it
all hangs together. We'll do this in your \\\$HOME for now, and you can move
it elsewhere later if you wish to.
"
tail="
NOTE: All the below stuff is on your *workstation*. You should not, normally,
have to do anything directly on your server to administer/use gitolite.
The admin repo is currently cloned at ~/gitolite-admin. You can reclone it
anywhere else if you wish. To administer gitolite, make changes to the config
file (conf/gitolite.conf) and/or the pubkeys (in subdirectory 'keydir') in any
clone, then git add, git commit, and git push.
ADDING REPOS: Do NOT add repos manually on the server. Edit the config file
to give *some* user access to the repo. When you push, an empty repo will be
created on the server.
ADDING USERS: copy their pubkey as keydir/<username>.pub, add it, commit and
push.
CONFIG FILE FORMAT: see comments in conf/example.conf in the gitolite source.
SSH MAGIC: Remember you (the admin) now have *two* keys to access the server
hosting your gitolite setup -- one to get you a command line, and one to get
you gitolite access; see doc/6-ssh-troubleshooting.mkd. If you're not using
keychain or some such software, you may have to run an 'ssh-add' command to
add that key each time you log in.
URLS: *Your* URL for cloning any repo on this server is different from the
url that the *other* users have to use. The easy install command should tell
you what these URLs look like, at the end of each successful run. Feel free
to re-run easy install again (using the same arguments) if you missed it.
UPGRADING GITOLITE: just pull a fresh clone from github, and run the same easy
install command as before, with the same arguments.
"

114
src/gl-install Executable file
View file

@ -0,0 +1,114 @@
#!/usr/bin/perl
# INTERNAL COMMAND. NOT MEANT TO BE RUN BY THE USER DIRECTLY.
use strict;
use warnings;
our ($REPO_BASE, $GL_ADMINDIR, $GL_CONF, $GIT_PATH, $GL_PACKAGE_CONF, $GL_PACKAGE_HOOKS, $GL_PERFLOGT, $REPO_UMASK);
# 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";
}
# the common setup module is in the same directory as this running program is
my $bindir = $0;
$bindir =~ s/\/[^\/]+$//;
require "$bindir/gitolite.pm";
# ask where the rc file is, get it, and "do" it
&where_is_rc();
unless ($ENV{GL_RC}) {
# doesn't exist. Copy it across, tell user to edit it and come back
my $glrc = $ENV{HOME} . "/.gitolite.rc";
if ($GL_PACKAGE_CONF) {
system("cp $GL_PACKAGE_CONF/example.gitolite.rc $glrc");
} else {
system("cp $bindir/../conf/example.gitolite.rc $glrc");
}
print "created $glrc\n";
print "please edit it, change the paths if you wish to, and RERUN THIS SCRIPT\n";
exit;
}
# ok now the rc file exists; read it to get the other paths
die "parse $ENV{GL_RC} failed: " . ($! or $@) unless do $ENV{GL_RC};
# add a custom path for git binaries, if specified
$ENV{PATH} .= ":$GIT_PATH" if $GIT_PATH;
# set the umask before creating any files/directories
umask($REPO_UMASK);
# mkdir $REPO_BASE, $GL_ADMINDIR if they don't already exist
$ENV{GL_REPO_BASE_ABS} = ( $REPO_BASE =~ m(^/) ? $REPO_BASE : "$ENV{HOME}/$REPO_BASE" );
wrap_mkdir($ENV{GL_REPO_BASE_ABS});
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 $bindir/../src $bindir/../doc $bindir/../hooks $GL_ADMINDIR");
system("cp $bindir/../conf/VERSION $GL_ADMINDIR/conf");
}
unless (-f $GL_CONF or $GL_PACKAGE_CONF) {
print <<EOF;
please do the following:
1. create and edit $GL_CONF to contain something like this:
repo gitolite-admin
RW+ = yourname
2. copy "yourname.pub" to $GL_ADMINDIR/keydir
3. run this command
$GL_ADMINDIR/src/gl-compile-conf
EOF
}
# finally, hooks must be propagated to all the repos in case they changed
chdir("$ENV{GL_REPO_BASE_ABS}") or die "chdir $ENV{GL_REPO_BASE_ABS} failed: $!\n";
for my $repo (`find . -type d -name "*.git"`) {
chomp ($repo);
# propagate our own, plus any local admin-defined, hooks
ln_sf("$GL_ADMINDIR/hooks/common", "*", "$repo/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/hooks") if $GL_PACKAGE_HOOKS;
chmod 0755, "$repo/hooks/update";
}
# oh and one of those repos is a bit more special and has an extra hook :)
if ( -d "gitolite-admin.git/hooks" ) {
print "copying post-update hook to gitolite-admin repo...\n";
unlink "gitolite-admin.git/hooks/post-update";
symlink "$GL_ADMINDIR/hooks/gitolite-admin/post-update", "gitolite-admin.git/hooks/post-update"
or die "could not symlink post-update hook\n";
# ditto... (see previous block)
ln_sf("$GL_PACKAGE_HOOKS/gitolite-admin", "post-update", "gitolite-admin.git/hooks") if $GL_PACKAGE_HOOKS;
chmod 0755, "gitolite-admin.git/hooks/post-update";
}
# fixup program renames
for my $oldname qw(pta-hook.sh conf-convert.pl 00-easy-install.sh 99-emergency-addkey.sh gl-emergency-addkey install.pl update-hook.pl hooks/update ga-post-update-hook VERSION) {
unlink "$GL_ADMINDIR/src/$oldname";
unlink "$ENV{HOME}/gitolite-install/src/$oldname";
}

23
src/gl-mirror-shell Executable file
View file

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

38
src/gl-mirror-sync Executable file
View file

@ -0,0 +1,38 @@
#!/bin/bash
mirror=$1
[ -z "$1" ] && { echo need \"user@host\" or ssh hostalias; exit 1; }
ssh -o PasswordAuthentication=no $mirror echo hello-there | grep hello-there >/dev/null ||
{ echo I cant ssh to $mirror; exit 1; }
cd $HOME
REPO_BASE=` cd $HOME;perl -e 'do ".gitolite.rc"; print $REPO_BASE'`
cd $REPO_BASE
ssh $mirror cat \$HOME/.gitolite.rc | expand | egrep '^ *\$GL_SLAVE_MODE *= *1; *$' >/dev/null || {
echo $mirror does not seem to be in slave mode
exit 1;
}
find . -type d -name "*.git" | cut -c3- | sort | while read r
do
cd $HOME; cd $REPO_BASE; cd $r
printf "$r "
if [ `git rev-parse HEAD` = "HEAD" ]
then
echo is empty\; skipping
continue
fi
# this is essentially the same code as in the post-receive hook
if git push --mirror $mirror:$r
then
:
else
ssh $mirror mkdir -p $r
ssh $mirror git init --bare $r
git push --mirror $mirror:$r ||
echo "WARNING: mirror push to $mirror failed"
fi < /dev/null
done

109
src/gl-setup Executable file
View file

@ -0,0 +1,109 @@
#!/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.
# Please see the doc/0-user-setup.mkd for details.
# 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 "$@"; exit 1; }
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
if [ -f $HOME/.gitolite.rc ]
then
perl -ne 's/^\s+//; s/[\s=].*//; print if /^\$/;' < $GL_PACKAGE_CONF/example.gitolite.rc | sort > .newvars
perl -ne 's/^\s+//; s/[\s=].*//; print if /^\$/;' < $HOME/.gitolite.rc | sort > .oldvars
comm -23 .newvars .oldvars > .diffvars
if [ -s .diffvars ]
then
cp $GL_PACKAGE_CONF/example.gitolite.rc $HOME/.gitolite.rc.new
echo new version of the rc file saved in $HOME/.gitolite.rc.new
echo
echo please update $HOME/.gitolite.rc manually if you need features
echo controlled by any of the following variables:
echo ----
sed -e 's/^/ /' < .diffvars
echo ----
fi
rm -f .newvars .oldvars .diffvars
else
[ -n "$GITOLITE_HTTP_HOME" ] || [ -n "$pubkey_file" ] || die "looks like first run -- I need a pubkey file"
[ -z "$GITOLITE_HTTP_HOME" ] || [ -n "$admin_name" ] || die "looks like first run -- I need an admin name"
cp $GL_PACKAGE_CONF/example.gitolite.rc $HOME/.gitolite.rc
printf "The default settings in the "rc" file ($HOME/.gitolite.rc) are fine for most\n"
printf "people but if you wish to make any changes, you can do so now.\n\nhit enter..."
read i
${EDITOR:-vi} $HOME/.gitolite.rc
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
)
# now we get to gitolite itself
gl-install -q
GL_ADMINDIR=` cd $HOME;perl -e 'do ".gitolite.rc"; print $GL_ADMINDIR'`
REPO_BASE=` cd $HOME;perl -e 'do ".gitolite.rc"; print $REPO_BASE' `
[ -f $GL_ADMINDIR/conf/gitolite.conf ] || {
cat <<EOF > $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
od=$PWD
cd; cd $REPO_BASE/gitolite-admin.git
GIT_WORK_TREE=$GL_ADMINDIR git add conf/gitolite.conf keydir
GIT_WORK_TREE=$GL_ADMINDIR git diff --cached --quiet 2>/dev/null || GIT_WORK_TREE=$GL_ADMINDIR git commit -am start
cd $od
# now that the admin repo is created, you have to set the hooks properly; best
# do it by running install again
gl-install -q

56
src/gl-setup-authkeys Executable file
View file

@ -0,0 +1,56 @@
#!/usr/bin/perl -w
# shim program
# arg-1: keydir
# - an external program populates "keydir" with *all* keys and then
# calls us, giving "keydir" as arg-1
# - we then call gitolite.pm's "setup_authkeys" function to do its thing
# IMPLEMENTATION NOTE: make sure this is in the same directory as
# "gitolite.pm" and all the rest of "src/".
# 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.
our ($GL_PERFLOGT);
# setup
my $bindir = $0;
$bindir =~ s/\/[^\/]+$//;
$bindir = "$ENV{PWD}/$bindir" unless $bindir =~ /^\//;
require "$bindir/gitolite.pm";
# prevent newbie from running it accidentally and clobbering his authkeys
# file!
if (@ARGV and $ARGV[0] eq '-batch') {
shift;
} else {
print STDERR "
This is a cronnable, batchable, program to rewrite ~/.ssh/authorized_keys
using public keys in a given directory.
If you are ABSOLUTELY sure you know what you're doing, here's how:
$0 -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($bindir, $keydir);

53
src/gl-system-install Executable file
View file

@ -0,0 +1,53 @@
#!/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
Example:
$0 /usr/local/bin /var/gitolite/conf /var/gitolite/hooks
In this example, all the binaries go to /usr/local/bin, and the shared
conf and hooks files/directories go to the other 2 directories given
"
exit 1;
}
die() { echo "$@"; echo; usage; exit 1; }
[ -z "$3" ] && usage
gl_bin_dir=$1
[ -d $gl_bin_dir ] || die "$gl_bin_dir not found"
gl_conf_dir=$2
[ -d $gl_conf_dir ] || die "$gl_conf_dir not found"
gl_hooks_dir=$3
[ -d $gl_hooks_dir ] || die "$gl_hooks_dir not found"
bindir=`echo $0 | perl -lpe 's/^/$ENV{PWD}\// unless /^\//; s/\/[^\/]+$//;'`
cd $bindir/.. # we assume the standard gitolite source tree is here!
cp src/* $gl_bin_dir || die "cp src/* to $gl_bin_dir failed"
rm $gl_bin_dir/gl-easy-install
perl -lpi -e "s(^GL_PACKAGE_CONF=.*)(GL_PACKAGE_CONF=$gl_conf_dir)" $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 HEAD 2>/dev/null > conf/VERSION || die "git describe failed -- this should not happen!"
else
[ -f conf/VERSION ] || echo '(unknown)' > conf/VERSION
fi
cp -R conf/* $gl_conf_dir || die "cp conf/* to $gl_conf_dir failed"
perl -lpi \
-e "s(^#\s*\\\$GL_PACKAGE_CONF\s*=.*)(\\\$GL_PACKAGE_CONF = '$gl_conf_dir';)" \
$gl_conf_dir/example.gitolite.rc
perl -lpi \
-e "s(^#\s*\\\$GL_PACKAGE_HOOKS\s*=.*)(\\\$GL_PACKAGE_HOOKS = '$gl_hooks_dir';)" \
$gl_conf_dir/example.gitolite.rc
cp -R hooks/* $gl_hooks_dir || die "cp hooks/* to $gl_hooks_dir failed"

40
src/gl-time Executable file
View file

@ -0,0 +1,40 @@
#!/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;
use Time::HiRes qw(gettimeofday tv_interval);
our ($GL_PERFLOGT);
# rc file
do "$ENV{HOME}/.gitolite.rc";
# this file is always in a fixed place; code in the main gitolite that
# seems to indicate it is not, is obsolete and needs to be fixed.
# the common setup module is in the same directory as this running program is
my $bindir = $0;
$bindir =~ s/\/[^\/]+$//;
$bindir = "$ENV{PWD}/$bindir" unless $bindir =~ /^\//;
require "$bindir/gitolite.pm";
# ---------------------------------------------------------------
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");

Some files were not shown because too many files have changed in this diff Show more