Compare commits
No commits in common. "temp-br--data-dumper-problem-demo" and "master" have entirely different histories.
temp-br--d
...
master
4
.gitattributes
vendored
4
.gitattributes
vendored
|
@ -1,4 +0,0 @@
|
||||||
conf/* crlf=input
|
|
||||||
src/* crlf=input
|
|
||||||
hooks/common/* crlf=input
|
|
||||||
hooks/gitolite-admin/* crlf=input
|
|
5
.gitignore
vendored
5
.gitignore
vendored
|
@ -1,5 +0,0 @@
|
||||||
*.tar
|
|
||||||
*.tgz
|
|
||||||
*.tar.gz
|
|
||||||
*.tar.bz2
|
|
||||||
conf/VERSION
|
|
86
CHANGELOG
Normal file
86
CHANGELOG
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
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.
|
|
@ -1,12 +1,12 @@
|
||||||
GNU GENERAL PUBLIC LICENSE
|
GNU GENERAL PUBLIC LICENSE
|
||||||
Version 2, June 1991
|
Version 2, June 1991
|
||||||
|
|
||||||
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
|
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
|
||||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
Everyone is permitted to copy and distribute verbatim copies
|
Everyone is permitted to copy and distribute verbatim copies
|
||||||
of this license document, but changing it is not allowed.
|
of this license document, but changing it is not allowed.
|
||||||
|
|
||||||
Preamble
|
Preamble
|
||||||
|
|
||||||
The licenses for most software are designed to take away your
|
The licenses for most software are designed to take away your
|
||||||
freedom to share and change it. By contrast, the GNU General Public
|
freedom to share and change it. By contrast, the GNU General Public
|
||||||
|
@ -56,7 +56,7 @@ patent must be licensed for everyone's free use or not licensed at all.
|
||||||
The precise terms and conditions for copying, distribution and
|
The precise terms and conditions for copying, distribution and
|
||||||
modification follow.
|
modification follow.
|
||||||
|
|
||||||
GNU GENERAL PUBLIC LICENSE
|
GNU GENERAL PUBLIC LICENSE
|
||||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||||
|
|
||||||
0. This License applies to any program or other work which contains
|
0. This License applies to any program or other work which contains
|
||||||
|
@ -255,7 +255,7 @@ make exceptions for this. Our decision will be guided by the two goals
|
||||||
of preserving the free status of all derivatives of our free software and
|
of preserving the free status of all derivatives of our free software and
|
||||||
of promoting the sharing and reuse of software generally.
|
of promoting the sharing and reuse of software generally.
|
||||||
|
|
||||||
NO WARRANTY
|
NO WARRANTY
|
||||||
|
|
||||||
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
||||||
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
||||||
|
@ -276,64 +276,3 @@ 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.
|
|
16
Makefile
16
Makefile
|
@ -1,16 +0,0 @@
|
||||||
# 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...
|
|
||||||
|
|
||||||
.GITOLITE-VERSION:
|
|
||||||
@touch conf/VERSION
|
|
||||||
|
|
||||||
%.tar: .GITOLITE-VERSION
|
|
||||||
git describe --tags --long $* > conf/VERSION
|
|
||||||
git archive $* > $@
|
|
||||||
tar -r -f $@ conf/VERSION
|
|
||||||
rm conf/VERSION
|
|
359
README.md
Normal file
359
README.md
Normal file
|
@ -0,0 +1,359 @@
|
||||||
|
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/
|
110
README.mkd
110
README.mkd
|
@ -1,110 +0,0 @@
|
||||||
# gitolite
|
|
||||||
|
|
||||||
> [Update 2009-10-28: apart from all the nifty new features, there's now an
|
|
||||||
> "easy install" script in the src directory. This script can be used to
|
|
||||||
> install as well as upgrade a gitolite install. Please see the INSTALL
|
|
||||||
> document for details]
|
|
||||||
|
|
||||||
----
|
|
||||||
|
|
||||||
Gitolite is a rewrite of gitosis, with a completely different config file that
|
|
||||||
allows (at last!) access control down to the branch level, including
|
|
||||||
specifying who can and cannot *rewind* a given branch.
|
|
||||||
|
|
||||||
In this document:
|
|
||||||
|
|
||||||
* what
|
|
||||||
* why
|
|
||||||
* extra features
|
|
||||||
* security
|
|
||||||
* contact and license
|
|
||||||
|
|
||||||
----
|
|
||||||
|
|
||||||
### what
|
|
||||||
|
|
||||||
Gitolite allows a server to host many git repositories and provide access to
|
|
||||||
many developers, without having to give them real userids on 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/` directory.
|
|
||||||
|
|
||||||
### why
|
|
||||||
|
|
||||||
I have been using gitosis for a while, and have learnt a lot from it. But in
|
|
||||||
a typical $DAYJOB setting, there are some issues:
|
|
||||||
|
|
||||||
* it's not always Linux; you can't just "urpmi gitosis" (or yum or apt-get)
|
|
||||||
and be done
|
|
||||||
* often, "python-setuptools" isn't installed (and on a Solaris9 I was trying
|
|
||||||
to help remotely, we never did manage to install it eventually)
|
|
||||||
* you don't have root access, or the ability to add users (this is also true
|
|
||||||
for people who have just one userid on a hosting provider)
|
|
||||||
* the most requested feature (see below) had to be written anyway
|
|
||||||
|
|
||||||
All of this pointed to a rewrite. In perl, naturally :-)
|
|
||||||
|
|
||||||
### extra 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 rolling my own gitosis in the first place.
|
|
||||||
|
|
||||||
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 [here][gsdiff].
|
|
||||||
|
|
||||||
[gsdiff]: http://github.com/sitaramc/gitolite/blob/pu/doc/3-faq-tips-etc.mkd#diff
|
|
||||||
|
|
||||||
* simpler, yet far more 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`)
|
|
||||||
* config file syntax gets checked upfront, and much more thoroughly
|
|
||||||
* if your requirements are still too complex, you can split up the config
|
|
||||||
file and delegate authority over parts of it
|
|
||||||
* easier to specify gitweb owner, description and gitweb/daemon access
|
|
||||||
* easier to sync gitweb (http) authorisation with gitolite's access config
|
|
||||||
* more 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
|
|
||||||
|
|
||||||
### 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 1000 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".
|
|
||||||
|
|
||||||
----
|
|
||||||
|
|
||||||
### contact and license
|
|
||||||
|
|
||||||
Gitolite is released under GPL v2. See COPYING for details.
|
|
||||||
|
|
||||||
sitaramc@gmail.com
|
|
99
check-g2-compat
Executable file
99
check-g2-compat
Executable file
|
@ -0,0 +1,99 @@
|
||||||
|
#!/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;
|
||||||
|
}
|
|
@ -1,298 +0,0 @@
|
||||||
# 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
|
|
||||||
|
|
||||||
# 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
|
|
||||||
|
|
||||||
# WILDCARD REPOSITORIES ("wildrepos" BRANCH ONLY)
|
|
||||||
# -----------------------------------------
|
|
||||||
|
|
||||||
# Please see doc/4-wildcard-repositories.mkd for details
|
|
||||||
|
|
||||||
# REPO AND BRANCH PERMISSIONS
|
|
||||||
# ---------------------------
|
|
||||||
|
|
||||||
# syntax:
|
|
||||||
# start line:
|
|
||||||
# repo [one or more repos and/or repo groups]
|
|
||||||
# followed by one or more permissions lines:
|
|
||||||
# (R|RW|RW+) [zero or more refexes] = [one or more users]
|
|
||||||
|
|
||||||
# there are 3 types of permissions: R, RW, and RW+. The "+" means permission
|
|
||||||
# to "rewind" (force push a non-fast forward to) a branch
|
|
||||||
|
|
||||||
# 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 already defined repos
|
|
||||||
# (a repository is defined if it has permission rules
|
|
||||||
# associated, empty "repo" stanza or "@group=..." line is
|
|
||||||
# not enough). *Please* do see doc/3-faq-tips-etc.mkd for
|
|
||||||
# some important 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
|
|
|
@ -1,201 +0,0 @@
|
||||||
# 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 :-)
|
|
||||||
|
|
||||||
# --------------------------------------
|
|
||||||
|
|
||||||
# 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";
|
|
||||||
|
|
||||||
# --------------------------------------
|
|
||||||
|
|
||||||
# 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";
|
|
||||||
|
|
||||||
# --------------------------------------
|
|
||||||
|
|
||||||
# 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";
|
|
||||||
|
|
||||||
# --------------------------------------
|
|
||||||
|
|
||||||
# personal branch prefix; leave it as is (empty) if you don't want to use the
|
|
||||||
# feature (see the "developer-specific branches" section in the "faq, tips,
|
|
||||||
# etc" document)
|
|
||||||
|
|
||||||
$PERSONAL="";
|
|
||||||
|
|
||||||
# uncomment one of these if you do want it. If you change it, remember it
|
|
||||||
# MUST start with "refs/"
|
|
||||||
|
|
||||||
# I recommend this:
|
|
||||||
# $PERSONAL="refs/personal";
|
|
||||||
|
|
||||||
# if you want something more visible/noisy, use this:
|
|
||||||
# $PERSONAL="refs/heads/personal";
|
|
||||||
|
|
||||||
# NOTE: whatever value you choose, for security reasons it is better to make
|
|
||||||
# it fully qualified -- that is, starting with "refs/"
|
|
||||||
|
|
||||||
# --------------------------------------
|
|
||||||
|
|
||||||
# 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/";
|
|
||||||
|
|
||||||
# --------------------------------------
|
|
||||||
|
|
||||||
# if you want to give shell access to any gitolite user(s), name them here.
|
|
||||||
# Please see doc/6-ssh-troubleshooting.mkd for details on how this works.
|
|
||||||
|
|
||||||
# Do not add people to this list indiscriminately. AUDITABILITY OF ACCESS
|
|
||||||
# CONTROL CHANGES (AND OF REPO ACCESSES) WILL BE COMPROMISED IF ADMINS CAN
|
|
||||||
# FIDDLE WITH THE ACTUAL (PLAIN TEXT) LOG FILES THAT GITOLITE KEEPS, WHICH
|
|
||||||
# THEY CAN EASILY DO IF THEY HAVE A SHELL.
|
|
||||||
|
|
||||||
# syntax: space separated list of gitolite usernames in *one* string variable.
|
|
||||||
# $SHELL_USERS = "alice bob";
|
|
||||||
|
|
||||||
# --------------------------------------
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# ----------------------------------------------------------------------
|
|
||||||
# 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 = ".*";
|
|
||||||
|
|
||||||
# --------------------------------------
|
|
||||||
# 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";
|
|
||||||
|
|
||||||
# --------------------------------------
|
|
||||||
# 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;
|
|
||||||
|
|
||||||
# --------------------------------------
|
|
||||||
# per perl rules, this should be the last line in such a file:
|
|
||||||
1;
|
|
||||||
|
|
||||||
# Local variables:
|
|
||||||
# mode: perl
|
|
||||||
# End:
|
|
||||||
# vim: set syn=perl:
|
|
|
@ -1,11 +0,0 @@
|
||||||
# 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
|
|
127
convert-gitosis-conf
Executable file
127
convert-gitosis-conf
Executable file
|
@ -0,0 +1,127 @@
|
||||||
|
#!/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";
|
||||||
|
#}
|
||||||
|
}
|
|
@ -1,712 +0,0 @@
|
||||||
#!/usr/bin/perl -w
|
|
||||||
|
|
||||||
# instructions
|
|
||||||
#
|
|
||||||
# run it as is, and the program segfaults (Data::Dumper returns undef)
|
|
||||||
# perl -w $0 | wc
|
|
||||||
#
|
|
||||||
# make it not use a sort sub, and program works; prints 18429 bytes to
|
|
||||||
# STDOUT:
|
|
||||||
# NO_SORT_SUB=1 perl -w $0 | wc
|
|
||||||
#
|
|
||||||
# run the bizarre workaround that slackorama found (issue #15 on gitolite's
|
|
||||||
# github repo), and the program works; prints 18429 bytes to STDOUT
|
|
||||||
# WTF=1 perl -w $0 | wc
|
|
||||||
#
|
|
||||||
# run it by populating the hash in one shot (not possible in real life), and
|
|
||||||
# the program works; prints 18429 bytes to STDOUT
|
|
||||||
# ONE_SHOT_SETUP=1 perl -w $0 | wc
|
|
||||||
|
|
||||||
# step 1 -- setup the %repos hash
|
|
||||||
our %repos = ();
|
|
||||||
if (exists $ENV{ONE_SHOT_SETUP}) {
|
|
||||||
# do it in one shot, from a fully populated hash directly coded into the
|
|
||||||
# program. This is not useful as a workaround for us in real life but it
|
|
||||||
# may help debugging the actual problem
|
|
||||||
&one_shot_setup();
|
|
||||||
} else {
|
|
||||||
# or do it in the sequence that gitolite "compile" step actually does
|
|
||||||
&real_life_setup();
|
|
||||||
}
|
|
||||||
|
|
||||||
use Data::Dumper;
|
|
||||||
$Data::Dumper::Indent = 1;
|
|
||||||
# sorting is not a problem...
|
|
||||||
$Data::Dumper::Sortkeys = 1;
|
|
||||||
|
|
||||||
# ...but a custom sort sub is! Not calling one helps, and is the official
|
|
||||||
# workaround for this problem, currently...
|
|
||||||
$Data::Dumper::Sortkeys = sub { return [ reverse sort keys %{$_[0]} ]; }
|
|
||||||
unless exists $ENV{NO_SORT_SUB};
|
|
||||||
|
|
||||||
# ...or you could use this totally meaningless operation too! The bizarreness
|
|
||||||
# of this has prompted me to write this test program
|
|
||||||
if (exists $ENV{WTF}) {
|
|
||||||
for my $key (sort keys %repos) {
|
|
||||||
my @wtf = sort keys %{ $repos{$key} };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
my $dumped_data = Data::Dumper->Dump([\%repos], [qw(*repos)]);
|
|
||||||
print $dumped_data;
|
|
||||||
print STDERR "dumped " . length($dumped_data) . " bytes\n";
|
|
||||||
|
|
||||||
sub one_shot_setup
|
|
||||||
{
|
|
||||||
# set up the %repos hash in one shot... in real life we cannot do this of
|
|
||||||
# course!
|
|
||||||
%repos = ('testing' => {'guser13' => [{'refs/.*' =>
|
|
||||||
'RW+'},{'refs/heads/master' => '-'}],'user88' =>
|
|
||||||
[{'refs/heads/fun/' => 'RW+'},{'refs/.*' => 'R'}],'user1' =>
|
|
||||||
[{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'guser76' =>
|
|
||||||
[{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'guser25' =>
|
|
||||||
[{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'user4' =>
|
|
||||||
[{'refs/.*' => 'RW+'},{'refs/heads/master' =>
|
|
||||||
'-'},{'refs/heads/master' => 'RW+'}],'guser36' => [{'refs/.*'
|
|
||||||
=> 'RW+'},{'refs/heads/master' => '-'}],'guser2' =>
|
|
||||||
[{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'guser14' =>
|
|
||||||
[{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'guser60' =>
|
|
||||||
[{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'guser57' =>
|
|
||||||
[{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'guser31' =>
|
|
||||||
[{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'guser7' =>
|
|
||||||
[{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'guser66' =>
|
|
||||||
[{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'guser75' =>
|
|
||||||
[{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'guser58' =>
|
|
||||||
[{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'guser1' =>
|
|
||||||
[{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'guser73' =>
|
|
||||||
[{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'guser35' =>
|
|
||||||
[{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'guser74' =>
|
|
||||||
[{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'user5' =>
|
|
||||||
[{'refs/.*' => 'RW+'},{'refs/heads/master' =>
|
|
||||||
'-'},{'refs/heads/master' => 'RW+'}],'guser11' => [{'refs/.*'
|
|
||||||
=> 'RW+'},{'refs/heads/master' => '-'}],'guser33' =>
|
|
||||||
[{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'guser53' =>
|
|
||||||
[{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'guser5' =>
|
|
||||||
[{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'guser4' =>
|
|
||||||
[{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'guser85' =>
|
|
||||||
[{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'guser50' =>
|
|
||||||
[{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'guser38' =>
|
|
||||||
[{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'guser59' =>
|
|
||||||
[{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'guser56' =>
|
|
||||||
[{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'user3' =>
|
|
||||||
[{'refs/.*' => 'RW+'},{'refs/heads/master' =>
|
|
||||||
'-'},{'refs/heads/master' => 'RW'},{'refs/heads/master' =>
|
|
||||||
'RW+'}],'guser54' => [{'refs/.*' =>
|
|
||||||
'RW+'},{'refs/heads/master' => '-'}],'guser20' => [{'refs/.*'
|
|
||||||
=> 'RW+'},{'refs/heads/master' => '-'}],'guser27' =>
|
|
||||||
[{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'user9' =>
|
|
||||||
[{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'guser0' =>
|
|
||||||
[{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'guser32' =>
|
|
||||||
[{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'user8' =>
|
|
||||||
[{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'guser41' =>
|
|
||||||
[{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'guser26' =>
|
|
||||||
[{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'guser18' =>
|
|
||||||
[{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'guser78' =>
|
|
||||||
[{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'guser52' =>
|
|
||||||
[{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'guser43' =>
|
|
||||||
[{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'guser22' =>
|
|
||||||
[{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'guser29' =>
|
|
||||||
[{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'guser64' =>
|
|
||||||
[{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'guser17' =>
|
|
||||||
[{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'guser34' =>
|
|
||||||
[{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'user2' =>
|
|
||||||
[{'refs/.*' => 'RW+'},{'refs/heads/master' =>
|
|
||||||
'-'},{'refs/heads/master' => 'RW'},{'refs/heads/master' =>
|
|
||||||
'RW+'}],'guser82' => [{'refs/.*' =>
|
|
||||||
'RW+'},{'refs/heads/master' => '-'}],'guser86' =>
|
|
||||||
[{'refs/heads/fun/' => 'RW+'},{'refs/.*' => 'R'}],'guser44' =>
|
|
||||||
[{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'guser62' =>
|
|
||||||
[{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'guser45' =>
|
|
||||||
[{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'guser48' =>
|
|
||||||
[{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'guser37' =>
|
|
||||||
[{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'user16' =>
|
|
||||||
[{'refs/.*' => 'RW+'},{'refs/heads/master' =>
|
|
||||||
'-'},{'refs/heads/master' => 'RW+'}],'user6' => [{'refs/.*' =>
|
|
||||||
'RW+'},{'refs/heads/master' => '-'}],'guser83' => [{'refs/.*'
|
|
||||||
=> 'RW+'},{'refs/heads/master' => '-'}],'user7' => [{'refs/.*'
|
|
||||||
=> 'RW+'},{'refs/heads/master' => '-'}],'user13' =>
|
|
||||||
[{'refs/.*' => 'RW+'},{'refs/heads/master' =>
|
|
||||||
'-'},{'refs/heads/master' => 'RW'}],'guser55' => [{'refs/.*'
|
|
||||||
=> 'RW+'},{'refs/heads/master' => '-'}],'user10' =>
|
|
||||||
[{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'W' =>
|
|
||||||
{'guser13' => 1,'user88' => 1,'user1' => 1,'guser76' =>
|
|
||||||
1,'guser25' => 1,'user4' => 1,'guser36' => 1,'guser2' =>
|
|
||||||
1,'guser14' => 1,'guser60' => 1,'guser57' => 1,'guser31' =>
|
|
||||||
1,'guser7' => 1,'guser66' => 1,'guser75' => 1,'guser58' =>
|
|
||||||
1,'guser1' => 1,'guser73' => 1,'guser35' => 1,'guser74' =>
|
|
||||||
1,'user5' => 1,'guser11' => 1,'guser33' => 1,'guser53' =>
|
|
||||||
1,'guser5' => 1,'guser4' => 1,'guser85' => 1,'guser50' =>
|
|
||||||
1,'guser38' => 1,'guser59' => 1,'guser56' => 1,'user3' =>
|
|
||||||
1,'guser54' => 1,'guser20' => 1,'guser27' => 1,'user9' =>
|
|
||||||
1,'guser0' => 1,'guser32' => 1,'user8' => 1,'guser41' =>
|
|
||||||
1,'guser26' => 1,'guser18' => 1,'guser78' => 1,'guser52' =>
|
|
||||||
1,'guser43' => 1,'guser22' => 1,'guser29' => 1,'guser64' =>
|
|
||||||
1,'guser17' => 1,'guser34' => 1,'user2' => 1,'guser82' =>
|
|
||||||
1,'guser86' => 1,'guser44' => 1,'guser62' => 1,'guser45' =>
|
|
||||||
1,'guser48' => 1,'guser37' => 1,'user16' => 1,'user6' =>
|
|
||||||
1,'guser83' => 1,'user7' => 1,'user13' => 1,'guser55' =>
|
|
||||||
1,'user10' => 1,'guser9' => 1,'guser8' => 1,'guser23' =>
|
|
||||||
1,'guser51' => 1,'guser68' => 1,'guser6' => 1,'guser69' =>
|
|
||||||
1,'user12' => 1,'guser21' => 1,'guser87' => 1,'user11' =>
|
|
||||||
1,'guser77' => 1,'guser63' => 1,'guser39' => 1,'guser79' =>
|
|
||||||
1,'guser49' => 1,'guser3' => 1,'guser84' => 1,'guser80' =>
|
|
||||||
1,'guser65' => 1,'guser10' => 1,'guser12' => 1,'guser42' =>
|
|
||||||
1,'user15' => 1,'guser15' => 1,'guser71' => 1,'@all' =>
|
|
||||||
1,'guser47' => 1,'guser40' => 1,'guser70' => 1,'guser28' =>
|
|
||||||
1,'guser67' => 1,'grussell' => 1,'guser19' => 1,'guser61' =>
|
|
||||||
1,'user14' => 1,'guser16' => 1,'guser81' => 1,'guser72' =>
|
|
||||||
1,'guser46' => 1,'guser30' => 1,'guser24' => 1},'guser9' =>
|
|
||||||
[{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'guser8' =>
|
|
||||||
[{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'guser23' =>
|
|
||||||
[{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'guser51' =>
|
|
||||||
[{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'guser68' =>
|
|
||||||
[{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'guser6' =>
|
|
||||||
[{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'guser69' =>
|
|
||||||
[{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'user12' =>
|
|
||||||
[{'refs/.*' => 'RW+'},{'refs/heads/master' =>
|
|
||||||
'-'},{'refs/heads/master' => 'RW'}],'guser21' => [{'refs/.*'
|
|
||||||
=> 'RW+'},{'refs/heads/master' => '-'}],'guser87' =>
|
|
||||||
[{'refs/heads/fun/' => 'RW+'},{'refs/.*' => 'R'}],'user11' =>
|
|
||||||
[{'refs/.*' => 'RW+'},{'refs/heads/master' =>
|
|
||||||
'-'},{'refs/heads/master' => 'RW'}],'guser77' => [{'refs/.*'
|
|
||||||
=> 'RW+'},{'refs/heads/master' => '-'}],'guser63' =>
|
|
||||||
[{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'guser39' =>
|
|
||||||
[{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'guser79' =>
|
|
||||||
[{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'guser49' =>
|
|
||||||
[{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'guser3' =>
|
|
||||||
[{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'guser84' =>
|
|
||||||
[{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'guser80' =>
|
|
||||||
[{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'guser65' =>
|
|
||||||
[{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'guser10' =>
|
|
||||||
[{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'guser12' =>
|
|
||||||
[{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'guser42' =>
|
|
||||||
[{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'user15' =>
|
|
||||||
[{'refs/.*' => 'RW+'},{'refs/heads/master' =>
|
|
||||||
'-'},{'refs/heads/master' => 'RW'}],'guser15' => [{'refs/.*'
|
|
||||||
=> 'RW+'},{'refs/heads/master' => '-'}],'guser71' =>
|
|
||||||
[{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'@all' =>
|
|
||||||
[{'refs/.*' => 'RW+'}],'guser47' => [{'refs/.*' =>
|
|
||||||
'RW+'},{'refs/heads/master' => '-'}],'guser40' => [{'refs/.*'
|
|
||||||
=> 'RW+'},{'refs/heads/master' => '-'}],'guser70' =>
|
|
||||||
[{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'guser28' =>
|
|
||||||
[{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'guser67' =>
|
|
||||||
[{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'grussell' =>
|
|
||||||
[{'refs/.*' => 'RW+'},{'refs/heads/master' =>
|
|
||||||
'-'},{'refs/heads/master' => 'RW+'}],'guser19' => [{'refs/.*'
|
|
||||||
=> 'RW+'},{'refs/heads/master' => '-'}],'guser61' =>
|
|
||||||
[{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'user14' =>
|
|
||||||
[{'refs/.*' => 'RW+'},{'refs/heads/master' =>
|
|
||||||
'-'},{'refs/heads/master' => 'RW'}],'guser16' => [{'refs/.*'
|
|
||||||
=> 'RW+'},{'refs/heads/master' => '-'}],'guser81' =>
|
|
||||||
[{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'guser72' =>
|
|
||||||
[{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'R' =>
|
|
||||||
{'guser13' => 1,'user88' => 1,'user1' => 1,'guser76' =>
|
|
||||||
1,'guser25' => 1,'user4' => 1,'guser36' => 1,'guser2' =>
|
|
||||||
1,'guser14' => 1,'guser60' => 1,'guser57' => 1,'guser31' =>
|
|
||||||
1,'guser7' => 1,'guser66' => 1,'guser75' => 1,'guser58' =>
|
|
||||||
1,'guser1' => 1,'guser73' => 1,'guser35' => 1,'guser74' =>
|
|
||||||
1,'user5' => 1,'guser11' => 1,'guser33' => 1,'guser53' =>
|
|
||||||
1,'guser5' => 1,'guser4' => 1,'guser85' => 1,'guser50' =>
|
|
||||||
1,'guser38' => 1,'guser59' => 1,'guser56' => 1,'user3' =>
|
|
||||||
1,'guser54' => 1,'guser20' => 1,'guser27' => 1,'user9' =>
|
|
||||||
1,'guser0' => 1,'guser32' => 1,'user8' => 1,'guser41' =>
|
|
||||||
1,'guser26' => 1,'guser18' => 1,'guser78' => 1,'guser52' =>
|
|
||||||
1,'guser43' => 1,'guser22' => 1,'guser29' => 1,'guser64' =>
|
|
||||||
1,'guser17' => 1,'guser34' => 1,'user2' => 1,'guser82' =>
|
|
||||||
1,'guser86' => 1,'guser44' => 1,'guser62' => 1,'guser45' =>
|
|
||||||
1,'guser48' => 1,'guser37' => 1,'user16' => 1,'user6' =>
|
|
||||||
1,'guser83' => 1,'user7' => 1,'user13' => 1,'guser55' =>
|
|
||||||
1,'user10' => 1,'guser9' => 1,'guser8' => 1,'guser23' =>
|
|
||||||
1,'guser51' => 1,'guser68' => 1,'guser6' => 1,'guser69' =>
|
|
||||||
1,'user12' => 1,'guser21' => 1,'guser87' => 1,'user11' =>
|
|
||||||
1,'guser77' => 1,'guser63' => 1,'guser39' => 1,'guser79' =>
|
|
||||||
1,'guser49' => 1,'guser3' => 1,'guser84' => 1,'guser80' =>
|
|
||||||
1,'guser65' => 1,'guser10' => 1,'guser12' => 1,'guser42' =>
|
|
||||||
1,'user15' => 1,'guser15' => 1,'guser71' => 1,'@all' =>
|
|
||||||
1,'guser47' => 1,'guser40' => 1,'guser70' => 1,'guser28' =>
|
|
||||||
1,'guser67' => 1,'grussell' => 1,'guser19' => 1,'guser61' =>
|
|
||||||
1,'user14' => 1,'guser16' => 1,'guser81' => 1,'guser72' =>
|
|
||||||
1,'guser46' => 1,'guser30' => 1,'guser24' => 1},'guser46' =>
|
|
||||||
[{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'guser30' =>
|
|
||||||
[{'refs/.*' => 'RW+'},{'refs/heads/master' => '-'}],'guser24' =>
|
|
||||||
[{'refs/.*' => 'RW+'},{'refs/heads/master' =>
|
|
||||||
'-'}]},'gitolite-admin' => {'W' => {'sitaram' => 1},'sitaram'
|
|
||||||
=> [{'refs/.*' => 'RW+'}],'R' => {'sitaram' => 1}});
|
|
||||||
}
|
|
||||||
|
|
||||||
sub real_life_setup {
|
|
||||||
# set up the %repos hash in a manner that reflects a real run of
|
|
||||||
# gitolite's "compiler" script:
|
|
||||||
$repos{'gitolite-admin'}{R}{'sitaram'} = 1;
|
|
||||||
$repos{'gitolite-admin'}{W}{'sitaram'} = 1;
|
|
||||||
push @{ $repos{'gitolite-admin'}{'sitaram'} }, { 'refs/.*' => 'RW+' };
|
|
||||||
$repos{'testing'}{R}{'@all'} = 1;
|
|
||||||
$repos{'testing'}{W}{'@all'} = 1;
|
|
||||||
push @{ $repos{'testing'}{'@all'} }, { 'refs/.*' => 'RW+' };
|
|
||||||
$repos{'testing'}{R}{'guser86'} = 1;
|
|
||||||
$repos{'testing'}{W}{'guser86'} = 1;
|
|
||||||
push @{ $repos{'testing'}{'guser86'} }, { 'refs/heads/fun/' => 'RW+' };
|
|
||||||
$repos{'testing'}{R}{'guser87'} = 1;
|
|
||||||
$repos{'testing'}{W}{'guser87'} = 1;
|
|
||||||
push @{ $repos{'testing'}{'guser87'} }, { 'refs/heads/fun/' => 'RW+' };
|
|
||||||
$repos{'testing'}{R}{'user88'} = 1;
|
|
||||||
$repos{'testing'}{W}{'user88'} = 1;
|
|
||||||
push @{ $repos{'testing'}{'user88'} }, { 'refs/heads/fun/' => 'RW+' };
|
|
||||||
$repos{'testing'}{R}{'guser86'} = 1;
|
|
||||||
push @{ $repos{'testing'}{'guser86'} }, { 'refs/.*' => 'R' };
|
|
||||||
$repos{'testing'}{R}{'guser87'} = 1;
|
|
||||||
push @{ $repos{'testing'}{'guser87'} }, { 'refs/.*' => 'R' };
|
|
||||||
$repos{'testing'}{R}{'user88'} = 1;
|
|
||||||
push @{ $repos{'testing'}{'user88'} }, { 'refs/.*' => 'R' };
|
|
||||||
$repos{'testing'}{R}{'grussell'} = 1;
|
|
||||||
$repos{'testing'}{W}{'grussell'} = 1;
|
|
||||||
push @{ $repos{'testing'}{'grussell'} }, { 'refs/.*' => 'RW+' };
|
|
||||||
$repos{'testing'}{R}{'guser0'} = 1;
|
|
||||||
$repos{'testing'}{W}{'guser0'} = 1;
|
|
||||||
push @{ $repos{'testing'}{'guser0'} }, { 'refs/.*' => 'RW+' };
|
|
||||||
$repos{'testing'}{R}{'guser1'} = 1;
|
|
||||||
$repos{'testing'}{W}{'guser1'} = 1;
|
|
||||||
push @{ $repos{'testing'}{'guser1'} }, { 'refs/.*' => 'RW+' };
|
|
||||||
$repos{'testing'}{R}{'guser10'} = 1;
|
|
||||||
$repos{'testing'}{W}{'guser10'} = 1;
|
|
||||||
push @{ $repos{'testing'}{'guser10'} }, { 'refs/.*' => 'RW+' };
|
|
||||||
$repos{'testing'}{R}{'guser11'} = 1;
|
|
||||||
$repos{'testing'}{W}{'guser11'} = 1;
|
|
||||||
push @{ $repos{'testing'}{'guser11'} }, { 'refs/.*' => 'RW+' };
|
|
||||||
$repos{'testing'}{R}{'guser12'} = 1;
|
|
||||||
$repos{'testing'}{W}{'guser12'} = 1;
|
|
||||||
push @{ $repos{'testing'}{'guser12'} }, { 'refs/.*' => 'RW+' };
|
|
||||||
$repos{'testing'}{R}{'guser13'} = 1;
|
|
||||||
$repos{'testing'}{W}{'guser13'} = 1;
|
|
||||||
push @{ $repos{'testing'}{'guser13'} }, { 'refs/.*' => 'RW+' };
|
|
||||||
$repos{'testing'}{R}{'guser14'} = 1;
|
|
||||||
$repos{'testing'}{W}{'guser14'} = 1;
|
|
||||||
push @{ $repos{'testing'}{'guser14'} }, { 'refs/.*' => 'RW+' };
|
|
||||||
$repos{'testing'}{R}{'guser15'} = 1;
|
|
||||||
$repos{'testing'}{W}{'guser15'} = 1;
|
|
||||||
push @{ $repos{'testing'}{'guser15'} }, { 'refs/.*' => 'RW+' };
|
|
||||||
$repos{'testing'}{R}{'guser16'} = 1;
|
|
||||||
$repos{'testing'}{W}{'guser16'} = 1;
|
|
||||||
push @{ $repos{'testing'}{'guser16'} }, { 'refs/.*' => 'RW+' };
|
|
||||||
$repos{'testing'}{R}{'guser17'} = 1;
|
|
||||||
$repos{'testing'}{W}{'guser17'} = 1;
|
|
||||||
push @{ $repos{'testing'}{'guser17'} }, { 'refs/.*' => 'RW+' };
|
|
||||||
$repos{'testing'}{R}{'guser18'} = 1;
|
|
||||||
$repos{'testing'}{W}{'guser18'} = 1;
|
|
||||||
push @{ $repos{'testing'}{'guser18'} }, { 'refs/.*' => 'RW+' };
|
|
||||||
$repos{'testing'}{R}{'guser19'} = 1;
|
|
||||||
$repos{'testing'}{W}{'guser19'} = 1;
|
|
||||||
push @{ $repos{'testing'}{'guser19'} }, { 'refs/.*' => 'RW+' };
|
|
||||||
$repos{'testing'}{R}{'guser2'} = 1;
|
|
||||||
$repos{'testing'}{W}{'guser2'} = 1;
|
|
||||||
push @{ $repos{'testing'}{'guser2'} }, { 'refs/.*' => 'RW+' };
|
|
||||||
$repos{'testing'}{R}{'guser20'} = 1;
|
|
||||||
$repos{'testing'}{W}{'guser20'} = 1;
|
|
||||||
push @{ $repos{'testing'}{'guser20'} }, { 'refs/.*' => 'RW+' };
|
|
||||||
$repos{'testing'}{R}{'guser21'} = 1;
|
|
||||||
$repos{'testing'}{W}{'guser21'} = 1;
|
|
||||||
push @{ $repos{'testing'}{'guser21'} }, { 'refs/.*' => 'RW+' };
|
|
||||||
$repos{'testing'}{R}{'guser22'} = 1;
|
|
||||||
$repos{'testing'}{W}{'guser22'} = 1;
|
|
||||||
push @{ $repos{'testing'}{'guser22'} }, { 'refs/.*' => 'RW+' };
|
|
||||||
$repos{'testing'}{R}{'guser23'} = 1;
|
|
||||||
$repos{'testing'}{W}{'guser23'} = 1;
|
|
||||||
push @{ $repos{'testing'}{'guser23'} }, { 'refs/.*' => 'RW+' };
|
|
||||||
$repos{'testing'}{R}{'guser24'} = 1;
|
|
||||||
$repos{'testing'}{W}{'guser24'} = 1;
|
|
||||||
push @{ $repos{'testing'}{'guser24'} }, { 'refs/.*' => 'RW+' };
|
|
||||||
$repos{'testing'}{R}{'guser25'} = 1;
|
|
||||||
$repos{'testing'}{W}{'guser25'} = 1;
|
|
||||||
push @{ $repos{'testing'}{'guser25'} }, { 'refs/.*' => 'RW+' };
|
|
||||||
$repos{'testing'}{R}{'guser26'} = 1;
|
|
||||||
$repos{'testing'}{W}{'guser26'} = 1;
|
|
||||||
push @{ $repos{'testing'}{'guser26'} }, { 'refs/.*' => 'RW+' };
|
|
||||||
$repos{'testing'}{R}{'guser27'} = 1;
|
|
||||||
$repos{'testing'}{W}{'guser27'} = 1;
|
|
||||||
push @{ $repos{'testing'}{'guser27'} }, { 'refs/.*' => 'RW+' };
|
|
||||||
$repos{'testing'}{R}{'guser28'} = 1;
|
|
||||||
$repos{'testing'}{W}{'guser28'} = 1;
|
|
||||||
push @{ $repos{'testing'}{'guser28'} }, { 'refs/.*' => 'RW+' };
|
|
||||||
$repos{'testing'}{R}{'guser29'} = 1;
|
|
||||||
$repos{'testing'}{W}{'guser29'} = 1;
|
|
||||||
push @{ $repos{'testing'}{'guser29'} }, { 'refs/.*' => 'RW+' };
|
|
||||||
$repos{'testing'}{R}{'guser3'} = 1;
|
|
||||||
$repos{'testing'}{W}{'guser3'} = 1;
|
|
||||||
push @{ $repos{'testing'}{'guser3'} }, { 'refs/.*' => 'RW+' };
|
|
||||||
$repos{'testing'}{R}{'guser30'} = 1;
|
|
||||||
$repos{'testing'}{W}{'guser30'} = 1;
|
|
||||||
push @{ $repos{'testing'}{'guser30'} }, { 'refs/.*' => 'RW+' };
|
|
||||||
$repos{'testing'}{R}{'guser31'} = 1;
|
|
||||||
$repos{'testing'}{W}{'guser31'} = 1;
|
|
||||||
push @{ $repos{'testing'}{'guser31'} }, { 'refs/.*' => 'RW+' };
|
|
||||||
$repos{'testing'}{R}{'guser32'} = 1;
|
|
||||||
$repos{'testing'}{W}{'guser32'} = 1;
|
|
||||||
push @{ $repos{'testing'}{'guser32'} }, { 'refs/.*' => 'RW+' };
|
|
||||||
$repos{'testing'}{R}{'guser33'} = 1;
|
|
||||||
$repos{'testing'}{W}{'guser33'} = 1;
|
|
||||||
push @{ $repos{'testing'}{'guser33'} }, { 'refs/.*' => 'RW+' };
|
|
||||||
$repos{'testing'}{R}{'guser34'} = 1;
|
|
||||||
$repos{'testing'}{W}{'guser34'} = 1;
|
|
||||||
push @{ $repos{'testing'}{'guser34'} }, { 'refs/.*' => 'RW+' };
|
|
||||||
$repos{'testing'}{R}{'guser35'} = 1;
|
|
||||||
$repos{'testing'}{W}{'guser35'} = 1;
|
|
||||||
push @{ $repos{'testing'}{'guser35'} }, { 'refs/.*' => 'RW+' };
|
|
||||||
$repos{'testing'}{R}{'guser36'} = 1;
|
|
||||||
$repos{'testing'}{W}{'guser36'} = 1;
|
|
||||||
push @{ $repos{'testing'}{'guser36'} }, { 'refs/.*' => 'RW+' };
|
|
||||||
$repos{'testing'}{R}{'guser37'} = 1;
|
|
||||||
$repos{'testing'}{W}{'guser37'} = 1;
|
|
||||||
push @{ $repos{'testing'}{'guser37'} }, { 'refs/.*' => 'RW+' };
|
|
||||||
$repos{'testing'}{R}{'guser38'} = 1;
|
|
||||||
$repos{'testing'}{W}{'guser38'} = 1;
|
|
||||||
push @{ $repos{'testing'}{'guser38'} }, { 'refs/.*' => 'RW+' };
|
|
||||||
$repos{'testing'}{R}{'guser39'} = 1;
|
|
||||||
$repos{'testing'}{W}{'guser39'} = 1;
|
|
||||||
push @{ $repos{'testing'}{'guser39'} }, { 'refs/.*' => 'RW+' };
|
|
||||||
$repos{'testing'}{R}{'guser4'} = 1;
|
|
||||||
$repos{'testing'}{W}{'guser4'} = 1;
|
|
||||||
push @{ $repos{'testing'}{'guser4'} }, { 'refs/.*' => 'RW+' };
|
|
||||||
$repos{'testing'}{R}{'guser40'} = 1;
|
|
||||||
$repos{'testing'}{W}{'guser40'} = 1;
|
|
||||||
push @{ $repos{'testing'}{'guser40'} }, { 'refs/.*' => 'RW+' };
|
|
||||||
$repos{'testing'}{R}{'guser41'} = 1;
|
|
||||||
$repos{'testing'}{W}{'guser41'} = 1;
|
|
||||||
push @{ $repos{'testing'}{'guser41'} }, { 'refs/.*' => 'RW+' };
|
|
||||||
$repos{'testing'}{R}{'guser42'} = 1;
|
|
||||||
$repos{'testing'}{W}{'guser42'} = 1;
|
|
||||||
push @{ $repos{'testing'}{'guser42'} }, { 'refs/.*' => 'RW+' };
|
|
||||||
$repos{'testing'}{R}{'guser43'} = 1;
|
|
||||||
$repos{'testing'}{W}{'guser43'} = 1;
|
|
||||||
push @{ $repos{'testing'}{'guser43'} }, { 'refs/.*' => 'RW+' };
|
|
||||||
$repos{'testing'}{R}{'guser44'} = 1;
|
|
||||||
$repos{'testing'}{W}{'guser44'} = 1;
|
|
||||||
push @{ $repos{'testing'}{'guser44'} }, { 'refs/.*' => 'RW+' };
|
|
||||||
$repos{'testing'}{R}{'guser45'} = 1;
|
|
||||||
$repos{'testing'}{W}{'guser45'} = 1;
|
|
||||||
push @{ $repos{'testing'}{'guser45'} }, { 'refs/.*' => 'RW+' };
|
|
||||||
$repos{'testing'}{R}{'guser46'} = 1;
|
|
||||||
$repos{'testing'}{W}{'guser46'} = 1;
|
|
||||||
push @{ $repos{'testing'}{'guser46'} }, { 'refs/.*' => 'RW+' };
|
|
||||||
$repos{'testing'}{R}{'guser47'} = 1;
|
|
||||||
$repos{'testing'}{W}{'guser47'} = 1;
|
|
||||||
push @{ $repos{'testing'}{'guser47'} }, { 'refs/.*' => 'RW+' };
|
|
||||||
$repos{'testing'}{R}{'guser48'} = 1;
|
|
||||||
$repos{'testing'}{W}{'guser48'} = 1;
|
|
||||||
push @{ $repos{'testing'}{'guser48'} }, { 'refs/.*' => 'RW+' };
|
|
||||||
$repos{'testing'}{R}{'guser49'} = 1;
|
|
||||||
$repos{'testing'}{W}{'guser49'} = 1;
|
|
||||||
push @{ $repos{'testing'}{'guser49'} }, { 'refs/.*' => 'RW+' };
|
|
||||||
$repos{'testing'}{R}{'guser5'} = 1;
|
|
||||||
$repos{'testing'}{W}{'guser5'} = 1;
|
|
||||||
push @{ $repos{'testing'}{'guser5'} }, { 'refs/.*' => 'RW+' };
|
|
||||||
$repos{'testing'}{R}{'guser50'} = 1;
|
|
||||||
$repos{'testing'}{W}{'guser50'} = 1;
|
|
||||||
push @{ $repos{'testing'}{'guser50'} }, { 'refs/.*' => 'RW+' };
|
|
||||||
$repos{'testing'}{R}{'guser51'} = 1;
|
|
||||||
$repos{'testing'}{W}{'guser51'} = 1;
|
|
||||||
push @{ $repos{'testing'}{'guser51'} }, { 'refs/.*' => 'RW+' };
|
|
||||||
$repos{'testing'}{R}{'guser52'} = 1;
|
|
||||||
$repos{'testing'}{W}{'guser52'} = 1;
|
|
||||||
push @{ $repos{'testing'}{'guser52'} }, { 'refs/.*' => 'RW+' };
|
|
||||||
$repos{'testing'}{R}{'guser53'} = 1;
|
|
||||||
$repos{'testing'}{W}{'guser53'} = 1;
|
|
||||||
push @{ $repos{'testing'}{'guser53'} }, { 'refs/.*' => 'RW+' };
|
|
||||||
$repos{'testing'}{R}{'guser54'} = 1;
|
|
||||||
$repos{'testing'}{W}{'guser54'} = 1;
|
|
||||||
push @{ $repos{'testing'}{'guser54'} }, { 'refs/.*' => 'RW+' };
|
|
||||||
$repos{'testing'}{R}{'guser55'} = 1;
|
|
||||||
$repos{'testing'}{W}{'guser55'} = 1;
|
|
||||||
push @{ $repos{'testing'}{'guser55'} }, { 'refs/.*' => 'RW+' };
|
|
||||||
$repos{'testing'}{R}{'guser56'} = 1;
|
|
||||||
$repos{'testing'}{W}{'guser56'} = 1;
|
|
||||||
push @{ $repos{'testing'}{'guser56'} }, { 'refs/.*' => 'RW+' };
|
|
||||||
$repos{'testing'}{R}{'guser57'} = 1;
|
|
||||||
$repos{'testing'}{W}{'guser57'} = 1;
|
|
||||||
push @{ $repos{'testing'}{'guser57'} }, { 'refs/.*' => 'RW+' };
|
|
||||||
$repos{'testing'}{R}{'guser58'} = 1;
|
|
||||||
$repos{'testing'}{W}{'guser58'} = 1;
|
|
||||||
push @{ $repos{'testing'}{'guser58'} }, { 'refs/.*' => 'RW+' };
|
|
||||||
$repos{'testing'}{R}{'guser59'} = 1;
|
|
||||||
$repos{'testing'}{W}{'guser59'} = 1;
|
|
||||||
push @{ $repos{'testing'}{'guser59'} }, { 'refs/.*' => 'RW+' };
|
|
||||||
$repos{'testing'}{R}{'guser6'} = 1;
|
|
||||||
$repos{'testing'}{W}{'guser6'} = 1;
|
|
||||||
push @{ $repos{'testing'}{'guser6'} }, { 'refs/.*' => 'RW+' };
|
|
||||||
$repos{'testing'}{R}{'guser60'} = 1;
|
|
||||||
$repos{'testing'}{W}{'guser60'} = 1;
|
|
||||||
push @{ $repos{'testing'}{'guser60'} }, { 'refs/.*' => 'RW+' };
|
|
||||||
$repos{'testing'}{R}{'guser61'} = 1;
|
|
||||||
$repos{'testing'}{W}{'guser61'} = 1;
|
|
||||||
push @{ $repos{'testing'}{'guser61'} }, { 'refs/.*' => 'RW+' };
|
|
||||||
$repos{'testing'}{R}{'guser62'} = 1;
|
|
||||||
$repos{'testing'}{W}{'guser62'} = 1;
|
|
||||||
push @{ $repos{'testing'}{'guser62'} }, { 'refs/.*' => 'RW+' };
|
|
||||||
$repos{'testing'}{R}{'guser63'} = 1;
|
|
||||||
$repos{'testing'}{W}{'guser63'} = 1;
|
|
||||||
push @{ $repos{'testing'}{'guser63'} }, { 'refs/.*' => 'RW+' };
|
|
||||||
$repos{'testing'}{R}{'guser64'} = 1;
|
|
||||||
$repos{'testing'}{W}{'guser64'} = 1;
|
|
||||||
push @{ $repos{'testing'}{'guser64'} }, { 'refs/.*' => 'RW+' };
|
|
||||||
$repos{'testing'}{R}{'guser65'} = 1;
|
|
||||||
$repos{'testing'}{W}{'guser65'} = 1;
|
|
||||||
push @{ $repos{'testing'}{'guser65'} }, { 'refs/.*' => 'RW+' };
|
|
||||||
$repos{'testing'}{R}{'guser66'} = 1;
|
|
||||||
$repos{'testing'}{W}{'guser66'} = 1;
|
|
||||||
push @{ $repos{'testing'}{'guser66'} }, { 'refs/.*' => 'RW+' };
|
|
||||||
$repos{'testing'}{R}{'guser67'} = 1;
|
|
||||||
$repos{'testing'}{W}{'guser67'} = 1;
|
|
||||||
push @{ $repos{'testing'}{'guser67'} }, { 'refs/.*' => 'RW+' };
|
|
||||||
$repos{'testing'}{R}{'guser68'} = 1;
|
|
||||||
$repos{'testing'}{W}{'guser68'} = 1;
|
|
||||||
push @{ $repos{'testing'}{'guser68'} }, { 'refs/.*' => 'RW+' };
|
|
||||||
$repos{'testing'}{R}{'guser69'} = 1;
|
|
||||||
$repos{'testing'}{W}{'guser69'} = 1;
|
|
||||||
push @{ $repos{'testing'}{'guser69'} }, { 'refs/.*' => 'RW+' };
|
|
||||||
$repos{'testing'}{R}{'guser7'} = 1;
|
|
||||||
$repos{'testing'}{W}{'guser7'} = 1;
|
|
||||||
push @{ $repos{'testing'}{'guser7'} }, { 'refs/.*' => 'RW+' };
|
|
||||||
$repos{'testing'}{R}{'guser70'} = 1;
|
|
||||||
$repos{'testing'}{W}{'guser70'} = 1;
|
|
||||||
push @{ $repos{'testing'}{'guser70'} }, { 'refs/.*' => 'RW+' };
|
|
||||||
$repos{'testing'}{R}{'guser71'} = 1;
|
|
||||||
$repos{'testing'}{W}{'guser71'} = 1;
|
|
||||||
push @{ $repos{'testing'}{'guser71'} }, { 'refs/.*' => 'RW+' };
|
|
||||||
$repos{'testing'}{R}{'guser72'} = 1;
|
|
||||||
$repos{'testing'}{W}{'guser72'} = 1;
|
|
||||||
push @{ $repos{'testing'}{'guser72'} }, { 'refs/.*' => 'RW+' };
|
|
||||||
$repos{'testing'}{R}{'guser73'} = 1;
|
|
||||||
$repos{'testing'}{W}{'guser73'} = 1;
|
|
||||||
push @{ $repos{'testing'}{'guser73'} }, { 'refs/.*' => 'RW+' };
|
|
||||||
$repos{'testing'}{R}{'guser74'} = 1;
|
|
||||||
$repos{'testing'}{W}{'guser74'} = 1;
|
|
||||||
push @{ $repos{'testing'}{'guser74'} }, { 'refs/.*' => 'RW+' };
|
|
||||||
$repos{'testing'}{R}{'guser75'} = 1;
|
|
||||||
$repos{'testing'}{W}{'guser75'} = 1;
|
|
||||||
push @{ $repos{'testing'}{'guser75'} }, { 'refs/.*' => 'RW+' };
|
|
||||||
$repos{'testing'}{R}{'guser76'} = 1;
|
|
||||||
$repos{'testing'}{W}{'guser76'} = 1;
|
|
||||||
push @{ $repos{'testing'}{'guser76'} }, { 'refs/.*' => 'RW+' };
|
|
||||||
$repos{'testing'}{R}{'guser77'} = 1;
|
|
||||||
$repos{'testing'}{W}{'guser77'} = 1;
|
|
||||||
push @{ $repos{'testing'}{'guser77'} }, { 'refs/.*' => 'RW+' };
|
|
||||||
$repos{'testing'}{R}{'guser78'} = 1;
|
|
||||||
$repos{'testing'}{W}{'guser78'} = 1;
|
|
||||||
push @{ $repos{'testing'}{'guser78'} }, { 'refs/.*' => 'RW+' };
|
|
||||||
$repos{'testing'}{R}{'guser79'} = 1;
|
|
||||||
$repos{'testing'}{W}{'guser79'} = 1;
|
|
||||||
push @{ $repos{'testing'}{'guser79'} }, { 'refs/.*' => 'RW+' };
|
|
||||||
$repos{'testing'}{R}{'guser8'} = 1;
|
|
||||||
$repos{'testing'}{W}{'guser8'} = 1;
|
|
||||||
push @{ $repos{'testing'}{'guser8'} }, { 'refs/.*' => 'RW+' };
|
|
||||||
$repos{'testing'}{R}{'guser80'} = 1;
|
|
||||||
$repos{'testing'}{W}{'guser80'} = 1;
|
|
||||||
push @{ $repos{'testing'}{'guser80'} }, { 'refs/.*' => 'RW+' };
|
|
||||||
$repos{'testing'}{R}{'guser81'} = 1;
|
|
||||||
$repos{'testing'}{W}{'guser81'} = 1;
|
|
||||||
push @{ $repos{'testing'}{'guser81'} }, { 'refs/.*' => 'RW+' };
|
|
||||||
$repos{'testing'}{R}{'guser82'} = 1;
|
|
||||||
$repos{'testing'}{W}{'guser82'} = 1;
|
|
||||||
push @{ $repos{'testing'}{'guser82'} }, { 'refs/.*' => 'RW+' };
|
|
||||||
$repos{'testing'}{R}{'guser83'} = 1;
|
|
||||||
$repos{'testing'}{W}{'guser83'} = 1;
|
|
||||||
push @{ $repos{'testing'}{'guser83'} }, { 'refs/.*' => 'RW+' };
|
|
||||||
$repos{'testing'}{R}{'guser84'} = 1;
|
|
||||||
$repos{'testing'}{W}{'guser84'} = 1;
|
|
||||||
push @{ $repos{'testing'}{'guser84'} }, { 'refs/.*' => 'RW+' };
|
|
||||||
$repos{'testing'}{R}{'guser85'} = 1;
|
|
||||||
$repos{'testing'}{W}{'guser85'} = 1;
|
|
||||||
push @{ $repos{'testing'}{'guser85'} }, { 'refs/.*' => 'RW+' };
|
|
||||||
$repos{'testing'}{R}{'guser9'} = 1;
|
|
||||||
$repos{'testing'}{W}{'guser9'} = 1;
|
|
||||||
push @{ $repos{'testing'}{'guser9'} }, { 'refs/.*' => 'RW+' };
|
|
||||||
$repos{'testing'}{R}{'user1'} = 1;
|
|
||||||
$repos{'testing'}{W}{'user1'} = 1;
|
|
||||||
push @{ $repos{'testing'}{'user1'} }, { 'refs/.*' => 'RW+' };
|
|
||||||
$repos{'testing'}{R}{'user10'} = 1;
|
|
||||||
$repos{'testing'}{W}{'user10'} = 1;
|
|
||||||
push @{ $repos{'testing'}{'user10'} }, { 'refs/.*' => 'RW+' };
|
|
||||||
$repos{'testing'}{R}{'user11'} = 1;
|
|
||||||
$repos{'testing'}{W}{'user11'} = 1;
|
|
||||||
push @{ $repos{'testing'}{'user11'} }, { 'refs/.*' => 'RW+' };
|
|
||||||
$repos{'testing'}{R}{'user12'} = 1;
|
|
||||||
$repos{'testing'}{W}{'user12'} = 1;
|
|
||||||
push @{ $repos{'testing'}{'user12'} }, { 'refs/.*' => 'RW+' };
|
|
||||||
$repos{'testing'}{R}{'user13'} = 1;
|
|
||||||
$repos{'testing'}{W}{'user13'} = 1;
|
|
||||||
push @{ $repos{'testing'}{'user13'} }, { 'refs/.*' => 'RW+' };
|
|
||||||
$repos{'testing'}{R}{'user14'} = 1;
|
|
||||||
$repos{'testing'}{W}{'user14'} = 1;
|
|
||||||
push @{ $repos{'testing'}{'user14'} }, { 'refs/.*' => 'RW+' };
|
|
||||||
$repos{'testing'}{R}{'user15'} = 1;
|
|
||||||
$repos{'testing'}{W}{'user15'} = 1;
|
|
||||||
push @{ $repos{'testing'}{'user15'} }, { 'refs/.*' => 'RW+' };
|
|
||||||
$repos{'testing'}{R}{'user16'} = 1;
|
|
||||||
$repos{'testing'}{W}{'user16'} = 1;
|
|
||||||
push @{ $repos{'testing'}{'user16'} }, { 'refs/.*' => 'RW+' };
|
|
||||||
$repos{'testing'}{R}{'user2'} = 1;
|
|
||||||
$repos{'testing'}{W}{'user2'} = 1;
|
|
||||||
push @{ $repos{'testing'}{'user2'} }, { 'refs/.*' => 'RW+' };
|
|
||||||
$repos{'testing'}{R}{'user3'} = 1;
|
|
||||||
$repos{'testing'}{W}{'user3'} = 1;
|
|
||||||
push @{ $repos{'testing'}{'user3'} }, { 'refs/.*' => 'RW+' };
|
|
||||||
$repos{'testing'}{R}{'user4'} = 1;
|
|
||||||
$repos{'testing'}{W}{'user4'} = 1;
|
|
||||||
push @{ $repos{'testing'}{'user4'} }, { 'refs/.*' => 'RW+' };
|
|
||||||
$repos{'testing'}{R}{'user5'} = 1;
|
|
||||||
$repos{'testing'}{W}{'user5'} = 1;
|
|
||||||
push @{ $repos{'testing'}{'user5'} }, { 'refs/.*' => 'RW+' };
|
|
||||||
$repos{'testing'}{R}{'user6'} = 1;
|
|
||||||
$repos{'testing'}{W}{'user6'} = 1;
|
|
||||||
push @{ $repos{'testing'}{'user6'} }, { 'refs/.*' => 'RW+' };
|
|
||||||
$repos{'testing'}{R}{'user7'} = 1;
|
|
||||||
$repos{'testing'}{W}{'user7'} = 1;
|
|
||||||
push @{ $repos{'testing'}{'user7'} }, { 'refs/.*' => 'RW+' };
|
|
||||||
$repos{'testing'}{R}{'user8'} = 1;
|
|
||||||
$repos{'testing'}{W}{'user8'} = 1;
|
|
||||||
push @{ $repos{'testing'}{'user8'} }, { 'refs/.*' => 'RW+' };
|
|
||||||
$repos{'testing'}{R}{'user9'} = 1;
|
|
||||||
$repos{'testing'}{W}{'user9'} = 1;
|
|
||||||
push @{ $repos{'testing'}{'user9'} }, { 'refs/.*' => 'RW+' };
|
|
||||||
push @{ $repos{'testing'}{'grussell'} }, { 'refs/heads/master' => '-' };
|
|
||||||
push @{ $repos{'testing'}{'guser0'} }, { 'refs/heads/master' => '-' };
|
|
||||||
push @{ $repos{'testing'}{'guser1'} }, { 'refs/heads/master' => '-' };
|
|
||||||
push @{ $repos{'testing'}{'guser10'} }, { 'refs/heads/master' => '-' };
|
|
||||||
push @{ $repos{'testing'}{'guser11'} }, { 'refs/heads/master' => '-' };
|
|
||||||
push @{ $repos{'testing'}{'guser12'} }, { 'refs/heads/master' => '-' };
|
|
||||||
push @{ $repos{'testing'}{'guser13'} }, { 'refs/heads/master' => '-' };
|
|
||||||
push @{ $repos{'testing'}{'guser14'} }, { 'refs/heads/master' => '-' };
|
|
||||||
push @{ $repos{'testing'}{'guser15'} }, { 'refs/heads/master' => '-' };
|
|
||||||
push @{ $repos{'testing'}{'guser16'} }, { 'refs/heads/master' => '-' };
|
|
||||||
push @{ $repos{'testing'}{'guser17'} }, { 'refs/heads/master' => '-' };
|
|
||||||
push @{ $repos{'testing'}{'guser18'} }, { 'refs/heads/master' => '-' };
|
|
||||||
push @{ $repos{'testing'}{'guser19'} }, { 'refs/heads/master' => '-' };
|
|
||||||
push @{ $repos{'testing'}{'guser2'} }, { 'refs/heads/master' => '-' };
|
|
||||||
push @{ $repos{'testing'}{'guser20'} }, { 'refs/heads/master' => '-' };
|
|
||||||
push @{ $repos{'testing'}{'guser21'} }, { 'refs/heads/master' => '-' };
|
|
||||||
push @{ $repos{'testing'}{'guser22'} }, { 'refs/heads/master' => '-' };
|
|
||||||
push @{ $repos{'testing'}{'guser23'} }, { 'refs/heads/master' => '-' };
|
|
||||||
push @{ $repos{'testing'}{'guser24'} }, { 'refs/heads/master' => '-' };
|
|
||||||
push @{ $repos{'testing'}{'guser25'} }, { 'refs/heads/master' => '-' };
|
|
||||||
push @{ $repos{'testing'}{'guser26'} }, { 'refs/heads/master' => '-' };
|
|
||||||
push @{ $repos{'testing'}{'guser27'} }, { 'refs/heads/master' => '-' };
|
|
||||||
push @{ $repos{'testing'}{'guser28'} }, { 'refs/heads/master' => '-' };
|
|
||||||
push @{ $repos{'testing'}{'guser29'} }, { 'refs/heads/master' => '-' };
|
|
||||||
push @{ $repos{'testing'}{'guser3'} }, { 'refs/heads/master' => '-' };
|
|
||||||
push @{ $repos{'testing'}{'guser30'} }, { 'refs/heads/master' => '-' };
|
|
||||||
push @{ $repos{'testing'}{'guser31'} }, { 'refs/heads/master' => '-' };
|
|
||||||
push @{ $repos{'testing'}{'guser32'} }, { 'refs/heads/master' => '-' };
|
|
||||||
push @{ $repos{'testing'}{'guser33'} }, { 'refs/heads/master' => '-' };
|
|
||||||
push @{ $repos{'testing'}{'guser34'} }, { 'refs/heads/master' => '-' };
|
|
||||||
push @{ $repos{'testing'}{'guser35'} }, { 'refs/heads/master' => '-' };
|
|
||||||
push @{ $repos{'testing'}{'guser36'} }, { 'refs/heads/master' => '-' };
|
|
||||||
push @{ $repos{'testing'}{'guser37'} }, { 'refs/heads/master' => '-' };
|
|
||||||
push @{ $repos{'testing'}{'guser38'} }, { 'refs/heads/master' => '-' };
|
|
||||||
push @{ $repos{'testing'}{'guser39'} }, { 'refs/heads/master' => '-' };
|
|
||||||
push @{ $repos{'testing'}{'guser4'} }, { 'refs/heads/master' => '-' };
|
|
||||||
push @{ $repos{'testing'}{'guser40'} }, { 'refs/heads/master' => '-' };
|
|
||||||
push @{ $repos{'testing'}{'guser41'} }, { 'refs/heads/master' => '-' };
|
|
||||||
push @{ $repos{'testing'}{'guser42'} }, { 'refs/heads/master' => '-' };
|
|
||||||
push @{ $repos{'testing'}{'guser43'} }, { 'refs/heads/master' => '-' };
|
|
||||||
push @{ $repos{'testing'}{'guser44'} }, { 'refs/heads/master' => '-' };
|
|
||||||
push @{ $repos{'testing'}{'guser45'} }, { 'refs/heads/master' => '-' };
|
|
||||||
push @{ $repos{'testing'}{'guser46'} }, { 'refs/heads/master' => '-' };
|
|
||||||
push @{ $repos{'testing'}{'guser47'} }, { 'refs/heads/master' => '-' };
|
|
||||||
push @{ $repos{'testing'}{'guser48'} }, { 'refs/heads/master' => '-' };
|
|
||||||
push @{ $repos{'testing'}{'guser49'} }, { 'refs/heads/master' => '-' };
|
|
||||||
push @{ $repos{'testing'}{'guser5'} }, { 'refs/heads/master' => '-' };
|
|
||||||
push @{ $repos{'testing'}{'guser50'} }, { 'refs/heads/master' => '-' };
|
|
||||||
push @{ $repos{'testing'}{'guser51'} }, { 'refs/heads/master' => '-' };
|
|
||||||
push @{ $repos{'testing'}{'guser52'} }, { 'refs/heads/master' => '-' };
|
|
||||||
push @{ $repos{'testing'}{'guser53'} }, { 'refs/heads/master' => '-' };
|
|
||||||
push @{ $repos{'testing'}{'guser54'} }, { 'refs/heads/master' => '-' };
|
|
||||||
push @{ $repos{'testing'}{'guser55'} }, { 'refs/heads/master' => '-' };
|
|
||||||
push @{ $repos{'testing'}{'guser56'} }, { 'refs/heads/master' => '-' };
|
|
||||||
push @{ $repos{'testing'}{'guser57'} }, { 'refs/heads/master' => '-' };
|
|
||||||
push @{ $repos{'testing'}{'guser58'} }, { 'refs/heads/master' => '-' };
|
|
||||||
push @{ $repos{'testing'}{'guser59'} }, { 'refs/heads/master' => '-' };
|
|
||||||
push @{ $repos{'testing'}{'guser6'} }, { 'refs/heads/master' => '-' };
|
|
||||||
push @{ $repos{'testing'}{'guser60'} }, { 'refs/heads/master' => '-' };
|
|
||||||
push @{ $repos{'testing'}{'guser61'} }, { 'refs/heads/master' => '-' };
|
|
||||||
push @{ $repos{'testing'}{'guser62'} }, { 'refs/heads/master' => '-' };
|
|
||||||
push @{ $repos{'testing'}{'guser63'} }, { 'refs/heads/master' => '-' };
|
|
||||||
push @{ $repos{'testing'}{'guser64'} }, { 'refs/heads/master' => '-' };
|
|
||||||
push @{ $repos{'testing'}{'guser65'} }, { 'refs/heads/master' => '-' };
|
|
||||||
push @{ $repos{'testing'}{'guser66'} }, { 'refs/heads/master' => '-' };
|
|
||||||
push @{ $repos{'testing'}{'guser67'} }, { 'refs/heads/master' => '-' };
|
|
||||||
push @{ $repos{'testing'}{'guser68'} }, { 'refs/heads/master' => '-' };
|
|
||||||
push @{ $repos{'testing'}{'guser69'} }, { 'refs/heads/master' => '-' };
|
|
||||||
push @{ $repos{'testing'}{'guser7'} }, { 'refs/heads/master' => '-' };
|
|
||||||
push @{ $repos{'testing'}{'guser70'} }, { 'refs/heads/master' => '-' };
|
|
||||||
push @{ $repos{'testing'}{'guser71'} }, { 'refs/heads/master' => '-' };
|
|
||||||
push @{ $repos{'testing'}{'guser72'} }, { 'refs/heads/master' => '-' };
|
|
||||||
push @{ $repos{'testing'}{'guser73'} }, { 'refs/heads/master' => '-' };
|
|
||||||
push @{ $repos{'testing'}{'guser74'} }, { 'refs/heads/master' => '-' };
|
|
||||||
push @{ $repos{'testing'}{'guser75'} }, { 'refs/heads/master' => '-' };
|
|
||||||
push @{ $repos{'testing'}{'guser76'} }, { 'refs/heads/master' => '-' };
|
|
||||||
push @{ $repos{'testing'}{'guser77'} }, { 'refs/heads/master' => '-' };
|
|
||||||
push @{ $repos{'testing'}{'guser78'} }, { 'refs/heads/master' => '-' };
|
|
||||||
push @{ $repos{'testing'}{'guser79'} }, { 'refs/heads/master' => '-' };
|
|
||||||
push @{ $repos{'testing'}{'guser8'} }, { 'refs/heads/master' => '-' };
|
|
||||||
push @{ $repos{'testing'}{'guser80'} }, { 'refs/heads/master' => '-' };
|
|
||||||
push @{ $repos{'testing'}{'guser81'} }, { 'refs/heads/master' => '-' };
|
|
||||||
push @{ $repos{'testing'}{'guser82'} }, { 'refs/heads/master' => '-' };
|
|
||||||
push @{ $repos{'testing'}{'guser83'} }, { 'refs/heads/master' => '-' };
|
|
||||||
push @{ $repos{'testing'}{'guser84'} }, { 'refs/heads/master' => '-' };
|
|
||||||
push @{ $repos{'testing'}{'guser85'} }, { 'refs/heads/master' => '-' };
|
|
||||||
push @{ $repos{'testing'}{'guser9'} }, { 'refs/heads/master' => '-' };
|
|
||||||
push @{ $repos{'testing'}{'user1'} }, { 'refs/heads/master' => '-' };
|
|
||||||
push @{ $repos{'testing'}{'user10'} }, { 'refs/heads/master' => '-' };
|
|
||||||
push @{ $repos{'testing'}{'user11'} }, { 'refs/heads/master' => '-' };
|
|
||||||
push @{ $repos{'testing'}{'user12'} }, { 'refs/heads/master' => '-' };
|
|
||||||
push @{ $repos{'testing'}{'user13'} }, { 'refs/heads/master' => '-' };
|
|
||||||
push @{ $repos{'testing'}{'user14'} }, { 'refs/heads/master' => '-' };
|
|
||||||
push @{ $repos{'testing'}{'user15'} }, { 'refs/heads/master' => '-' };
|
|
||||||
push @{ $repos{'testing'}{'user16'} }, { 'refs/heads/master' => '-' };
|
|
||||||
push @{ $repos{'testing'}{'user2'} }, { 'refs/heads/master' => '-' };
|
|
||||||
push @{ $repos{'testing'}{'user3'} }, { 'refs/heads/master' => '-' };
|
|
||||||
push @{ $repos{'testing'}{'user4'} }, { 'refs/heads/master' => '-' };
|
|
||||||
push @{ $repos{'testing'}{'user5'} }, { 'refs/heads/master' => '-' };
|
|
||||||
push @{ $repos{'testing'}{'user6'} }, { 'refs/heads/master' => '-' };
|
|
||||||
push @{ $repos{'testing'}{'user7'} }, { 'refs/heads/master' => '-' };
|
|
||||||
push @{ $repos{'testing'}{'user8'} }, { 'refs/heads/master' => '-' };
|
|
||||||
push @{ $repos{'testing'}{'user9'} }, { 'refs/heads/master' => '-' };
|
|
||||||
$repos{'testing'}{R}{'user11'} = 1;
|
|
||||||
$repos{'testing'}{W}{'user11'} = 1;
|
|
||||||
push @{ $repos{'testing'}{'user11'} }, { 'refs/heads/master' => 'RW' };
|
|
||||||
$repos{'testing'}{R}{'user12'} = 1;
|
|
||||||
$repos{'testing'}{W}{'user12'} = 1;
|
|
||||||
push @{ $repos{'testing'}{'user12'} }, { 'refs/heads/master' => 'RW' };
|
|
||||||
$repos{'testing'}{R}{'user13'} = 1;
|
|
||||||
$repos{'testing'}{W}{'user13'} = 1;
|
|
||||||
push @{ $repos{'testing'}{'user13'} }, { 'refs/heads/master' => 'RW' };
|
|
||||||
$repos{'testing'}{R}{'user14'} = 1;
|
|
||||||
$repos{'testing'}{W}{'user14'} = 1;
|
|
||||||
push @{ $repos{'testing'}{'user14'} }, { 'refs/heads/master' => 'RW' };
|
|
||||||
$repos{'testing'}{R}{'user15'} = 1;
|
|
||||||
$repos{'testing'}{W}{'user15'} = 1;
|
|
||||||
push @{ $repos{'testing'}{'user15'} }, { 'refs/heads/master' => 'RW' };
|
|
||||||
$repos{'testing'}{R}{'user2'} = 1;
|
|
||||||
$repos{'testing'}{W}{'user2'} = 1;
|
|
||||||
push @{ $repos{'testing'}{'user2'} }, { 'refs/heads/master' => 'RW' };
|
|
||||||
$repos{'testing'}{R}{'user3'} = 1;
|
|
||||||
$repos{'testing'}{W}{'user3'} = 1;
|
|
||||||
push @{ $repos{'testing'}{'user3'} }, { 'refs/heads/master' => 'RW' };
|
|
||||||
$repos{'testing'}{R}{'grussell'} = 1;
|
|
||||||
$repos{'testing'}{W}{'grussell'} = 1;
|
|
||||||
push @{ $repos{'testing'}{'grussell'} }, { 'refs/heads/master' => 'RW+' };
|
|
||||||
$repos{'testing'}{R}{'user16'} = 1;
|
|
||||||
$repos{'testing'}{W}{'user16'} = 1;
|
|
||||||
push @{ $repos{'testing'}{'user16'} }, { 'refs/heads/master' => 'RW+' };
|
|
||||||
$repos{'testing'}{R}{'user2'} = 1;
|
|
||||||
$repos{'testing'}{W}{'user2'} = 1;
|
|
||||||
push @{ $repos{'testing'}{'user2'} }, { 'refs/heads/master' => 'RW+' };
|
|
||||||
$repos{'testing'}{R}{'user3'} = 1;
|
|
||||||
$repos{'testing'}{W}{'user3'} = 1;
|
|
||||||
push @{ $repos{'testing'}{'user3'} }, { 'refs/heads/master' => 'RW+' };
|
|
||||||
$repos{'testing'}{R}{'user4'} = 1;
|
|
||||||
$repos{'testing'}{W}{'user4'} = 1;
|
|
||||||
push @{ $repos{'testing'}{'user4'} }, { 'refs/heads/master' => 'RW+' };
|
|
||||||
$repos{'testing'}{R}{'user5'} = 1;
|
|
||||||
$repos{'testing'}{W}{'user5'} = 1;
|
|
||||||
push @{ $repos{'testing'}{'user5'} }, { 'refs/heads/master' => 'RW+' };
|
|
||||||
}
|
|
|
@ -1,335 +0,0 @@
|
||||||
# installing gitolite
|
|
||||||
|
|
||||||
[Update 2009-11-18: easy install now works from msysgit also!]
|
|
||||||
|
|
||||||
Gitolite is somewhat unusual as far as "server" software goes -- every userid
|
|
||||||
on the system is a potential "gitolite host" and can install his own version
|
|
||||||
if he chooses to.
|
|
||||||
|
|
||||||
This document tells you how to install gitolite. After the install is done,
|
|
||||||
you may want to see the [admin document][admin] for adding users, repos, etc.
|
|
||||||
|
|
||||||
**Please note** that gitolite depends heavily on proper ssh setup and pubkey
|
|
||||||
based access. Sadly, most people don't know ssh as well as they think they
|
|
||||||
do. To make matters worse, ssh problems in gitolite don't always look like
|
|
||||||
ssh problems. Please read about [ssh troubleshooting][doc6] if you have *any*
|
|
||||||
kind of trouble installing gitolite!
|
|
||||||
|
|
||||||
[admin]: http://github.com/sitaramc/gitolite/blob/pu/doc/2-admin.mkd
|
|
||||||
[doc6]: http://github.com/sitaramc/gitolite/blob/pu/doc/6-ssh-troubleshooting.mkd
|
|
||||||
|
|
||||||
In this document:
|
|
||||||
|
|
||||||
* install methods
|
|
||||||
* user install
|
|
||||||
* typical example run
|
|
||||||
* advantages over the older install methods
|
|
||||||
* disadvantages
|
|
||||||
* upgrades
|
|
||||||
* other notes
|
|
||||||
* system install / user setup
|
|
||||||
* next steps
|
|
||||||
* appendix A: server and client requirements for user install
|
|
||||||
* server
|
|
||||||
* install workstation
|
|
||||||
* admin workstation(s)
|
|
||||||
* appendix B: uninstalling gitolite
|
|
||||||
* appendix C: NOTE TO PACKAGE MAINTAINERS
|
|
||||||
|
|
||||||
----
|
|
||||||
|
|
||||||
### install methods
|
|
||||||
|
|
||||||
There are 2 ways to install gitolite: The **user-install** mode was the
|
|
||||||
traditional way, and is used when *any* of the following is true:
|
|
||||||
|
|
||||||
* you don't have root on your "server" (some types of hosting setups, many
|
|
||||||
corporate paranoia setups ;-)
|
|
||||||
* your server distro does not have gitolite in its package repositories
|
|
||||||
* your server distro's package repositories have an old version of gitolite
|
|
||||||
* you want to stay current with the latest gitolite versions
|
|
||||||
* your server is not Linux (maybe AIX, or Solaris, etc.)
|
|
||||||
|
|
||||||
The "user install" section describes this method.
|
|
||||||
|
|
||||||
The **system-install followed by user-setup** mode is used when you (or
|
|
||||||
someone who has root) has installed an RPM or DEB of gitolite and you intend
|
|
||||||
to use that version.
|
|
||||||
|
|
||||||
The "system install / user setup" section describes this method.
|
|
||||||
|
|
||||||
----
|
|
||||||
|
|
||||||
### user install
|
|
||||||
|
|
||||||
There is an easy install script that makes installing very easy for the common
|
|
||||||
case. **This script will setup everything on the server, but you have to run
|
|
||||||
it on your workstation, NOT on the server!**
|
|
||||||
|
|
||||||
Assumptions/pre-requisites:
|
|
||||||
|
|
||||||
* you have a server to host gitolite
|
|
||||||
* git is installed on that server (and so is perl)
|
|
||||||
* you have a userid on that server
|
|
||||||
* you have ssh-pubkey (**password-less**) login to that userid
|
|
||||||
* if you have only password access, run `ssh-keygen -t rsa` to create a
|
|
||||||
new keypair if needed, then run `ssh-copy-id user@host`. If you do
|
|
||||||
not have `ssh-copy-id`, read doc/3-faq-tips-etc.mkd and look for
|
|
||||||
`ssh-copy-id` in that file for instructions
|
|
||||||
* you have a clone or an archive of gitolite somewhere on your workstation
|
|
||||||
* if you don't have one, just run `git clone git://github.com/sitaramc/gitolite`
|
|
||||||
* your workstation has bash (even msysgit bash will do)
|
|
||||||
|
|
||||||
Once you have all this, just `cd` to that clone and run `src/gl-easy-install`
|
|
||||||
and follow the prompts! (Running it without any arguments shows you usage
|
|
||||||
plus other useful info).
|
|
||||||
|
|
||||||
#### typical example run
|
|
||||||
|
|
||||||
A typical run for me is:
|
|
||||||
|
|
||||||
src/gl-easy-install -q git my.git.server sitaram
|
|
||||||
|
|
||||||
`-q` stands for "quiet" mode -- very minimal output, no verbose descriptions
|
|
||||||
of what it is going to do, and no pauses unless absolutely needed. However,
|
|
||||||
if you're doing this for the first time or you appreciate knowing what it is
|
|
||||||
actually doing, I suggest you skip the `-q`.
|
|
||||||
|
|
||||||
#### advantages over the older install methods
|
|
||||||
|
|
||||||
* all ssh problems reduced to **just one pre-requisite**: enable ssh pubkey
|
|
||||||
(password-less) access to the server from your workstation first
|
|
||||||
* the script takes care of all the server side work
|
|
||||||
* when done:
|
|
||||||
* you get two different pubkeys (the original one for command line
|
|
||||||
access as before, plus a new one, created by the script, for gitolite
|
|
||||||
access)
|
|
||||||
* you can admin gitolite by commit+push a "gitolite-admin" repo, just
|
|
||||||
like gitosis (i.e., full "push to admin" power!)
|
|
||||||
|
|
||||||
#### disadvantages
|
|
||||||
|
|
||||||
* need a recent bash
|
|
||||||
|
|
||||||
#### upgrades
|
|
||||||
|
|
||||||
Upgrading gitolite is easy.
|
|
||||||
|
|
||||||
To upgrade, pull the latest "master" (or other) branch in your gitolite repo
|
|
||||||
clone, then run the same exact command you ran to do the install, except you
|
|
||||||
can leave out the last argument.
|
|
||||||
|
|
||||||
And you might want to add a `-q` to speed things up :-)
|
|
||||||
|
|
||||||
Note that this only upgrades the software. Unlike earlier versions, it does
|
|
||||||
**not** touch the `conf/gitolite.conf` file or the contents of `keydir` in any
|
|
||||||
way. I decided that it is not possible to **safely** let an upgrade do
|
|
||||||
something meaningful with them -- fiddling with existing config files (as
|
|
||||||
opposed to merely creating one which did not exist) is best left to a human.
|
|
||||||
|
|
||||||
#### other notes
|
|
||||||
|
|
||||||
* if you run `src/gl-easy-install` without the `-q` option, you will be
|
|
||||||
given a chance to edit `~/.gitolite.rc`. You can change any options (such
|
|
||||||
as paths, for instance), but be sure to keep the perl syntax -- you
|
|
||||||
*don't* have to know perl to do so, it's fairly easy to guess in this
|
|
||||||
limited case.
|
|
||||||
|
|
||||||
### system install / user setup
|
|
||||||
|
|
||||||
In this mode a system administrator installs gitolite using the server's
|
|
||||||
distro package mechanism (yum install, apt-get install, etc).
|
|
||||||
|
|
||||||
Once this is done, you as a user must run a command like this (unlike in the
|
|
||||||
"user install" mode, this is done directly on the server):
|
|
||||||
|
|
||||||
gl-setup yourname.pub
|
|
||||||
|
|
||||||
where yourname.pub is a copy of a public key from your workstation. The first
|
|
||||||
time you run this, it will create a "gitolite-admin" repo and populate it with
|
|
||||||
the right configuration for whoever has the corresponding private key to
|
|
||||||
clone and push it. In other words, that person is the administrator for this
|
|
||||||
particular gitolite instance.
|
|
||||||
|
|
||||||
If your system administrator upgrades gitolite itself, things will usually
|
|
||||||
just work without any change; you should not have to do anything special.
|
|
||||||
However, some new features may require additional settings in your
|
|
||||||
`~/.gitolite.rc` file.
|
|
||||||
|
|
||||||
Finally, in the rare case that you managed to lose your keys to the admin repo
|
|
||||||
and want to supply a new pubkey, you can use this command to replace any such
|
|
||||||
key. Could be useful in an emergency -- just get your new "yourname.pub" to
|
|
||||||
the server and run the same command as above.
|
|
||||||
|
|
||||||
**IMPORTANT**: there are two variables in the `~/.gitolite.rc` file:
|
|
||||||
`$GL_PACKAGE_CONF` and `$GL_PACKAGE_HOOKS`. If you remove or change either of
|
|
||||||
them, expect trouble :-)
|
|
||||||
|
|
||||||
### next steps
|
|
||||||
|
|
||||||
The last message produced by the easy install script should tell you how to
|
|
||||||
add users, repos, etc., and you will find more details in the [admin][admin]
|
|
||||||
document.
|
|
||||||
|
|
||||||
<a name="server_reqs"></a>
|
|
||||||
|
|
||||||
### appendix A: server and client requirements for user install
|
|
||||||
|
|
||||||
There are 3 machines *potentially* involved in installing and administering
|
|
||||||
gitolite.
|
|
||||||
|
|
||||||
#### server
|
|
||||||
|
|
||||||
This is where gitolite is eventually installed. You need a *normal* userid
|
|
||||||
(typically "git" but can be anything) on this machine; root access is *not*
|
|
||||||
needed, but it has to be some sort of Unix (not Windows).
|
|
||||||
|
|
||||||
You need the following software on it:
|
|
||||||
|
|
||||||
* git
|
|
||||||
* 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
|
|
||||||
* default install is fine; no special modules are needed
|
|
||||||
* (a normal install of git also requires/installs perl, so you probably
|
|
||||||
have it already)
|
|
||||||
* openssh server
|
|
||||||
* (I guess any ssh server that can understand the `authorized_keys` file
|
|
||||||
format should work)
|
|
||||||
|
|
||||||
#### install workstation
|
|
||||||
|
|
||||||
Installing or upgrading the gitolite software itself is best done by running
|
|
||||||
the easy-install program from a gitolite clone.
|
|
||||||
|
|
||||||
This script is heavily dependent on bash, so you need a machine with a bash
|
|
||||||
shell. Even the bash that comes with msysgit is fine, if you don't have a
|
|
||||||
Linux box handy.
|
|
||||||
|
|
||||||
If you have neither Linux nor Windows+msysgit, you still have a few
|
|
||||||
alternatives:
|
|
||||||
|
|
||||||
* use a different userid on the same server (assuming it has bash)
|
|
||||||
* use the same userid on the same server (same assumption)
|
|
||||||
* manually simulate the script directly on the server (doable, but tedious)
|
|
||||||
|
|
||||||
#### admin workstation(s)
|
|
||||||
|
|
||||||
When you install gitolite, it creates a repository called "gitolite-admin" and
|
|
||||||
gives you permissions on it.
|
|
||||||
|
|
||||||
Administering gitolite (adding repos/users, assigning permissions, etc) is
|
|
||||||
done by cloning this repo, making changes to a file called
|
|
||||||
`conf/gitolite.conf`, adding users' pubkeys to `keydir/`, and pushing the
|
|
||||||
changes back to the server.
|
|
||||||
|
|
||||||
Which means all this can be done from *any* machine. You'll normally do it
|
|
||||||
from the same machine you used to install gitolite, but it doesn't have to be
|
|
||||||
the same one, as long as your pubkey has been added and permissions given to
|
|
||||||
allow you to push to the gitolite-admin repo.
|
|
||||||
|
|
||||||
<a name="uninstall"></a>
|
|
||||||
|
|
||||||
### appendix B: 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]. 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/
|
|
||||||
|
|
||||||
Uninstalling gitolite is fairly easy. Just log on to the server and do the
|
|
||||||
following (assuming `$REPO_BASE` in the rc file was left at its default of
|
|
||||||
`~/repositories`; if not, adjust accordingly):
|
|
||||||
|
|
||||||
* 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
|
|
||||||
|
|
||||||
* Then 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.
|
|
||||||
|
|
||||||
* Finally, 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.
|
|
||||||
|
|
||||||
### appendix C: NOTE TO PACKAGE MAINTAINERS
|
|
||||||
|
|
||||||
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.
|
|
|
@ -1,90 +0,0 @@
|
||||||
# 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:
|
|
||||||
|
|
||||||
[inst]: http://github.com/sitaramc/gitolite/blob/pu/doc/0-INSTALL.mkd
|
|
||||||
|
|
||||||
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: 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*
|
|
||||||
|
|
||||||
5. 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
|
|
||||||
|
|
135
doc/2-admin.mkd
135
doc/2-admin.mkd
|
@ -1,135 +0,0 @@
|
||||||
# administering and running gitolite
|
|
||||||
|
|
||||||
*Note*: some of the paths in this document use variable names. Just refer to
|
|
||||||
`~/.gitolite.rc` for the correct values for *your* installation.
|
|
||||||
|
|
||||||
In this document:
|
|
||||||
|
|
||||||
* administer
|
|
||||||
* adding users and repos
|
|
||||||
* moving pre-existing repos into gitolite
|
|
||||||
* specifying gitweb and daemon access
|
|
||||||
* custom hooks
|
|
||||||
* custom git config
|
|
||||||
|
|
||||||
### administer
|
|
||||||
|
|
||||||
First of all, ***do NOT add new repos manually***, unless you know how to add
|
|
||||||
the required hook as well. Without the hook, branch-level access control will
|
|
||||||
not work for that repo, which sorta defeats the idea of using gitolite :-)
|
|
||||||
|
|
||||||
Please read on to see how to do this correctly.
|
|
||||||
|
|
||||||
#### 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
|
|
||||||
|
|
||||||
[genpub]: http://sitaramc.github.com/0-installing/2-access-gitolite.html#generating_a_public_key
|
|
||||||
|
|
||||||
* 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
|
|
||||||
|
|
||||||
* 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
|
|
||||||
|
|
||||||
#### 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, and then, from an
|
|
||||||
existing clone, "push --all" to the new one.
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
#### 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.
|
|
||||||
|
|
||||||
To make a repo or repo group accessible via "git daemon", just give read
|
|
||||||
permission to the special user "daemon". See the [faq, tips, etc][ss]
|
|
||||||
document for easy ways to specify access for multiple repositories.
|
|
||||||
|
|
||||||
[ss]: http://github.com/sitaramc/gitolite/blob/pu/doc/3-faq-tips-etc.mkd#gwd
|
|
||||||
|
|
||||||
There's a special user called "gitweb" also, which works the same way.
|
|
||||||
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/daemon -- that is
|
|
||||||
a one-time setup you must do separately. All this 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.
|
|
||||||
|
|
||||||
#### custom hooks
|
|
||||||
|
|
||||||
If you want to put in your own, custom, hooks every time a new repo is created
|
|
||||||
by gitolite, put a **tested** hook script in `hooks/common` of your gitolite
|
|
||||||
clone before running easy-install. As distributed, there are only two files
|
|
||||||
there, but everything (*everything*) in that directory will get copied to the
|
|
||||||
`hooks/` subdirectory of every *new* repo created.
|
|
||||||
|
|
||||||
In order to push a new or updated hook script to *existing* repos as well,
|
|
||||||
just run easy install once again; it'll do it to existing repos also.
|
|
||||||
|
|
||||||
**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.**
|
|
||||||
|
|
||||||
#### 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.
|
|
|
@ -1,686 +0,0 @@
|
||||||
# assorted faqs, tips, and notes on gitolite
|
|
||||||
|
|
||||||
In this document:
|
|
||||||
|
|
||||||
* common errors and mistakes
|
|
||||||
* git version dependency
|
|
||||||
* other errors, warnings, notes...
|
|
||||||
* ssh-copy-id
|
|
||||||
* cloning an empty repo
|
|
||||||
* `@all` syntax for repos
|
|
||||||
* umask setting
|
|
||||||
* getting a tar file from a clone
|
|
||||||
* features
|
|
||||||
* syntax and normal usage
|
|
||||||
* simpler syntax
|
|
||||||
* one user, many keys
|
|
||||||
* security, access control, and auditing
|
|
||||||
* two levels of access rights checking
|
|
||||||
* better logging
|
|
||||||
* "exclude" (or "deny") rules
|
|
||||||
* file/dir NAME based restrictions
|
|
||||||
* delegating parts of the config file
|
|
||||||
* convenience features
|
|
||||||
* what repos do I have access to?
|
|
||||||
* error checking the config file
|
|
||||||
* including config lines from other files
|
|
||||||
* support for git installed outside default PATH
|
|
||||||
* "personal" branches
|
|
||||||
* custom hooks and custom git config
|
|
||||||
* helping with gitweb
|
|
||||||
* easier to specify gitweb "description" and gitweb/daemon access
|
|
||||||
* easier to link gitweb authorisation with gitolite
|
|
||||||
* advanced features
|
|
||||||
* repos named with wildcards
|
|
||||||
* access control for external commands
|
|
||||||
* design choices
|
|
||||||
* keeping the parser and the access control separate
|
|
||||||
|
|
||||||
## 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/6-ssh-troubleshooting.mkd for what all this means.
|
|
||||||
|
|
||||||
## git version dependency
|
|
||||||
|
|
||||||
Here's a workaround for a version dependency that the normal flow of gitolite
|
|
||||||
has.
|
|
||||||
|
|
||||||
When you edit your config file to create a new repo, and push the changes to
|
|
||||||
the server, gitolite creates an empty, bare repo for you. Normally, you're
|
|
||||||
expected to clone this on the client side, and start working -- make your
|
|
||||||
first commit(s), then push, etc.
|
|
||||||
|
|
||||||
However, cloning an empty repo requires a server side git version that is at
|
|
||||||
least 1.6.2. Gitolite detects this when creating a repo, and warns you.
|
|
||||||
|
|
||||||
The workaround is to use the older (gitosis-style) method on the client:
|
|
||||||
create an empty repo locally, make a commit or two, set an "origin" remote,
|
|
||||||
and then push. Something like:
|
|
||||||
|
|
||||||
mkdir my-new-project
|
|
||||||
cd my-new-project
|
|
||||||
git init
|
|
||||||
git commit --allow-empty -m 'Initial repository'
|
|
||||||
# or, if your client side git is too old for --allow-empty, just make some
|
|
||||||
# files, "git add" them, then "git commit"
|
|
||||||
git remote add origin git@gitolite-server:my-new-project.git
|
|
||||||
git push origin master:master
|
|
||||||
|
|
||||||
Once this is done, the repo is available for cloning by anyone else in the
|
|
||||||
normal way, since it's not empty anymore.
|
|
||||||
|
|
||||||
## other errors, warnings, notes...
|
|
||||||
|
|
||||||
### 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!)]
|
|
||||||
|
|
||||||
### cloning an empty repo
|
|
||||||
|
|
||||||
Cloning an empty repo is only possible with clients greater than 1.6.2. So at
|
|
||||||
least one of your clients needs to have a recent git. Once at least one
|
|
||||||
commit has been made, older clients can also use it
|
|
||||||
|
|
||||||
When you clone an empty repo, git seems to complain about `fatal: The remote
|
|
||||||
end hung up unexpectedly`. However, you can ignore this, since it doesn't
|
|
||||||
seem to hurt anything. [Update 2009-09-14; this has been fixed in git
|
|
||||||
1.6.4.3]
|
|
||||||
|
|
||||||
### `@all` syntax for repos
|
|
||||||
|
|
||||||
There *is* a way to use the `@all` syntax for repos also, as described in
|
|
||||||
`conf/example.conf`. However, there is an important difference between this
|
|
||||||
and the old `@all` (for users):
|
|
||||||
|
|
||||||
* `@all` for repos is immediately expanded, when found, into the currently
|
|
||||||
known list of repos. "Currently" means upto this point in the config
|
|
||||||
file, and "known" means having some user with some permissions associated
|
|
||||||
with the repo!
|
|
||||||
|
|
||||||
* This means that if you really want *all* repos, you'd better put this para
|
|
||||||
at the **end** of the config file!
|
|
||||||
|
|
||||||
### 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
|
|
||||||
|
|
||||||
## 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
|
|
||||||
fearures than the original goal of "gitosis + branch-level access control".
|
|
||||||
|
|
||||||
### 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>
|
|
||||||
|
|
||||||
#### 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?
|
|
||||||
|
|
||||||
In gitosis, the admin creates a single "sitaram.pub" containing one line for
|
|
||||||
each of my pubkeys. In gitolite, we keep them separate: "sitaram@laptop.pub"
|
|
||||||
and "sitaram@desktop.pub". The part before the "@" is the username, so
|
|
||||||
gitolite knows these two keys belong to the same person.
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
### security, access control, and auditing
|
|
||||||
|
|
||||||
<a name="two_levels"></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 (this is the only check that gitosis has). 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.
|
|
||||||
|
|
||||||
#### 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.
|
|
||||||
|
|
||||||
#### "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
|
|
||||||
|
|
||||||
#### 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.
|
|
||||||
|
|
||||||
#### 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
|
|
||||||
[doc/5-delegation.mkd](http://github.com/sitaramc/gitolite/blob/pu/doc/5-delegation.mkd)
|
|
||||||
for details.
|
|
||||||
|
|
||||||
### convenience features
|
|
||||||
|
|
||||||
<a name="myrights"></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.
|
|
||||||
|
|
||||||
Easy! Just use ssh and try to log in as if you were attempting to get a
|
|
||||||
shell:
|
|
||||||
|
|
||||||
$ ssh gitolite info
|
|
||||||
PTY allocation request failed on channel 0
|
|
||||||
hello sitaram, the gitolite version here is v0.6-17-g94ed189
|
|
||||||
you have the following permissions:
|
|
||||||
R W Anu-WSD
|
|
||||||
R ROtest
|
|
||||||
R W SecureBrowse
|
|
||||||
R W entrans
|
|
||||||
R W git-notes
|
|
||||||
R W gitolite
|
|
||||||
R W gitolite-admin
|
|
||||||
R W indic_web_input
|
|
||||||
R W proxy
|
|
||||||
@ @ testing
|
|
||||||
R W vkc
|
|
||||||
|
|
||||||
Note that until this version, we used to put out an ugly `need
|
|
||||||
SSH_ORIGINAL_COMMAND` error, just like gitosis used to. All we did is put
|
|
||||||
that code path to better use :-)
|
|
||||||
|
|
||||||
#### error checking the config file
|
|
||||||
|
|
||||||
gitosis does not do any. I just found out that if you mis-spell `members` as
|
|
||||||
`member`, gitosis will silently ignore it, and leave you wondering why access
|
|
||||||
was denied.
|
|
||||||
|
|
||||||
Gitolite "compiles" the config file first and keyword typos *are* caught so
|
|
||||||
you know right away.
|
|
||||||
|
|
||||||
#### including config lines from other files
|
|
||||||
|
|
||||||
See the entry under "INCLUDE SOME OTHER FILE" in `conf/example.conf`.
|
|
||||||
|
|
||||||
#### 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 :-)
|
|
||||||
|
|
||||||
#### "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>/*`), with full
|
|
||||||
permissions for that dev and read-only for everyone else. And you get
|
|
||||||
this without adding a single line to the access config file -- pretty
|
|
||||||
much fire and forget as far as the admin is concerned, even if there is
|
|
||||||
constant churn in the project teams.
|
|
||||||
|
|
||||||
Not bad for something that took just *one* line of code to implement.
|
|
||||||
And that's one clean, readable, line, by the way ;-)
|
|
||||||
|
|
||||||
The admin would set `$PERSONAL_BRANCH_PREFIX` in the rc file and communicate
|
|
||||||
this to all users. It could be something like `refs/heads/personal`, which
|
|
||||||
means all such branches will show up in `git branch` lookups and `git clone`
|
|
||||||
will fetch them. Or he could use, say, `refs/personal`, which means it won't
|
|
||||||
show up in any normal "branch-y" commands and stuff, and generally be much
|
|
||||||
less noisy.
|
|
||||||
|
|
||||||
**Note that a user who has NO write access cannot have personal branches**; if
|
|
||||||
you read the section (above) on "two levels of access rights checking" you'll
|
|
||||||
understand why.
|
|
||||||
|
|
||||||
For instance, in the following example, `user3` cannot push to any
|
|
||||||
`refs/heads/personal/user3/*` branches because the first level check stops him
|
|
||||||
cold:
|
|
||||||
|
|
||||||
# assume $PERSONAL = 'refs/heads/personal' in ~/.gitolite.rc
|
|
||||||
repo myrepo
|
|
||||||
RW+ master = sitaram
|
|
||||||
RW+ release = qa_guy
|
|
||||||
RW = user1 user2
|
|
||||||
R = user3
|
|
||||||
|
|
||||||
If we relax that check, *any* access becomes *write* access. Yes it will be
|
|
||||||
caught later, by the hook, but it's good practice to catch things in multiple
|
|
||||||
places.
|
|
||||||
|
|
||||||
If you want `user3` to have his own personal branch, but without write access
|
|
||||||
to any of the "real" branches (like "master", "release", etc.), just use a
|
|
||||||
dummy branch. Choose a name that will never exist in practice, or even if
|
|
||||||
someone creates it, we don't care. For example, this will get him past the
|
|
||||||
first check:
|
|
||||||
|
|
||||||
RW dummy = user3
|
|
||||||
|
|
||||||
Just don't *show* the user this config file; it might sound insulting :-)
|
|
||||||
|
|
||||||
#### 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="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!
|
|
||||||
|
|
||||||
#### easier to specify gitweb "description" and gitweb/daemon access
|
|
||||||
|
|
||||||
To enable access to a repo via gitweb *and* create a "description" for it to
|
|
||||||
show up on the webpage, just add a line like this, anywhere in the config
|
|
||||||
file:
|
|
||||||
|
|
||||||
reponame = "one line of description"
|
|
||||||
|
|
||||||
You can also specify an "owner":
|
|
||||||
|
|
||||||
reponame "owner name" = "one line of description"
|
|
||||||
|
|
||||||
To enable access to one or more repos via git daemon, just give "read"
|
|
||||||
permissions to the special username `daemon`.
|
|
||||||
|
|
||||||
There is also a special user called `gitweb` to specify gitweb access; useful
|
|
||||||
if you don't care about specifying individual descriptions for each repo and
|
|
||||||
just want to quickly enable gitweb access to one or more repos.
|
|
||||||
|
|
||||||
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="gitwebauth"></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
|
|
||||||
|
|
||||||
Here's the rest of how it hangs together.
|
|
||||||
|
|
||||||
Gitweb allows you to specify a subroutine to decide on access. We use that
|
|
||||||
feature and tie it to gitolite. Sample code (untested by me, but others do
|
|
||||||
use it, munged from something I saw [here][leho]) is given below.
|
|
||||||
|
|
||||||
Note the **utter simplicity** of the actual check (just 1 line!). This is an
|
|
||||||
unexpected piece of luck coming from the decision to keep the config parse
|
|
||||||
separate from the actual access control. The config parser puts a pure perl
|
|
||||||
hash in that file named below as `$gl_conf_compiled`, so all the parsing is
|
|
||||||
already done and we just use it!
|
|
||||||
|
|
||||||
# completely untested... but the basic idea should work fine
|
|
||||||
|
|
||||||
# change these as needed
|
|
||||||
# projectroot should be the same as gitolite's REPO_BASE, but converted to
|
|
||||||
# an absolute path
|
|
||||||
$projectroot = '/home/git/repositories/';
|
|
||||||
my $gl_conf_compiled = '/home/git/.gitolite/conf/gitolite.conf-compiled.pm';
|
|
||||||
|
|
||||||
# I am told this gives us the HTTP auth username
|
|
||||||
my $username = $cgi->remote_user;
|
|
||||||
|
|
||||||
# ----------
|
|
||||||
|
|
||||||
# parse the config file; updates %repos hash
|
|
||||||
our %repos;
|
|
||||||
die "parse $gl_conf_compiled failed: " . ($! or $@) unless do $gl_conf_compiled;
|
|
||||||
|
|
||||||
# this is gitweb's mechanism; it calls whatever sub is pointed at by this
|
|
||||||
# variable to decide access yes/no. Gitweb calls it with one argument
|
|
||||||
# containing the full path of the repo being accessed
|
|
||||||
$export_auth_hook = sub {
|
|
||||||
my $reponame = shift;
|
|
||||||
# take the full path provided, strip the beginning...
|
|
||||||
$reponame =~ s/\Q$projectroot\E\/?//;
|
|
||||||
# ...and the end, to get the repo name as it is specified in gitolite conf
|
|
||||||
$reponame =~ s/\.git$//;
|
|
||||||
|
|
||||||
return exists $repos{$reponame}{R}{$username}
|
|
||||||
|| exists $repos{$reponame}{R}{'@all'};
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
[leho]: http://leho.kraav.com/news/2009/10/27/using-apache-authentication-with-gitweb-gitosis-repository-access-control/
|
|
||||||
|
|
||||||
### advanced features
|
|
||||||
|
|
||||||
#### repos named with wildcards
|
|
||||||
|
|
||||||
Please see `doc/4-wildcard-repositories.mkd` for all the details.
|
|
||||||
|
|
||||||
#### 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. The first (and only, so far) such command implemented is rsync.
|
|
||||||
|
|
||||||
Note that this is incompatible with giving people shell access as described in
|
|
||||||
`doc/6-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).
|
|
||||||
|
|
||||||
Please see the config files (both of them) for examples and usage.
|
|
||||||
|
|
||||||
## design choices
|
|
||||||
|
|
||||||
### 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.
|
|
|
@ -1,216 +0,0 @@
|
||||||
# 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.
|
|
||||||
|
|
||||||
Also, there are some limitations. For example, you cannot specify gitconfig
|
|
||||||
values for a wildcard repo; it only works for actual repos.
|
|
||||||
|
|
||||||
There may be other such missing features. Sometimes it's just not possible to
|
|
||||||
make it work. Or it may be cumbersome enough that unless there are *no*
|
|
||||||
workarounds I may not have the time to code it right away.
|
|
||||||
|
|
||||||
----
|
|
||||||
|
|
||||||
In this document:
|
|
||||||
|
|
||||||
* wildcard repos
|
|
||||||
* wildcard repos with creater name in them
|
|
||||||
* wildcard repos without creater name in them
|
|
||||||
* side-note: line-anchored regexes
|
|
||||||
* contrast with refexes
|
|
||||||
* handing out rights to wildcard-matched repos
|
|
||||||
* setting a gitweb description for a wildcard-matched repo
|
|
||||||
* reporting
|
|
||||||
* other issues and discussion
|
|
||||||
|
|
||||||
This document is mostly "by example".
|
|
||||||
|
|
||||||
----
|
|
||||||
|
|
||||||
### Wildcard repos
|
|
||||||
|
|
||||||
Which of these alternatives you choose depends on your needs, and the social
|
|
||||||
aspects of your environment. The first one is a little more rigid, making it
|
|
||||||
harder to make mistakes, and the second is more flexible and trusting.
|
|
||||||
|
|
||||||
#### Wildcard repos with creater name in them
|
|
||||||
|
|
||||||
Here's an example snippet:
|
|
||||||
|
|
||||||
@prof = u1
|
|
||||||
@TAs = u2 u3
|
|
||||||
@students = u4 u5 u6
|
|
||||||
|
|
||||||
repo assignments/CREATER/a[0-9][0-9]
|
|
||||||
C = @students
|
|
||||||
RW+ = CREATER
|
|
||||||
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 ;-) Now
|
|
||||||
make some changes and push, and after that, that specific repo
|
|
||||||
(`assignments/u4/a12`) behaves as if the access control looked like this:
|
|
||||||
|
|
||||||
# effective config
|
|
||||||
repo assignments/u4/a12
|
|
||||||
RW+ = u4
|
|
||||||
RW = WRITERS @TAs
|
|
||||||
R = READERS @prof
|
|
||||||
|
|
||||||
#### Wildcard repos without creater name in them
|
|
||||||
|
|
||||||
Here's how the same example would look if you did not want the CREATER's name
|
|
||||||
to be part of the actual repo name.
|
|
||||||
|
|
||||||
repo assignments/a[0-9][0-9]
|
|
||||||
C = @students
|
|
||||||
RW+ = CREATER
|
|
||||||
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.
|
|
||||||
|
|
||||||
### Side-note: Line-anchored regexes
|
|
||||||
|
|
||||||
A regex like
|
|
||||||
|
|
||||||
repo assignments/S[0-9]+/A[0-9]+
|
|
||||||
|
|
||||||
would match `assignments/S02/A37`. It will not match `assignments/S02/ABC`,
|
|
||||||
or `assignments/S02/a37`, obviously.
|
|
||||||
|
|
||||||
But you may be surprised to find that it does not match even
|
|
||||||
`assignments/S02/A37/B99`. This is because internally, gitolite
|
|
||||||
*line-anchors* the given regex; so that regex actually becomes
|
|
||||||
`^assignments/S[0-9]+/A[0-9]+$` -- notice the line beginning and ending
|
|
||||||
metacharacters.
|
|
||||||
|
|
||||||
#### Contrast with refexes
|
|
||||||
|
|
||||||
Just for interest, note that this is in contrast to the refexes for the normal
|
|
||||||
"branch" permissions, as described in `conf/example.conf` and elsewhere.
|
|
||||||
Those "refexes" are *not* anchored; a pattern like `refs/heads/master`
|
|
||||||
actually matches `foo/refs/heads/master01/bar` as well, even if no one will
|
|
||||||
actually push such a branch! You can anchor it if you really care, by using
|
|
||||||
`master$` instead of `master`, but anchoring is *not* the default for
|
|
||||||
refexes.]
|
|
||||||
|
|
||||||
### 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 creater 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.
|
|
||||||
|
|
||||||
### setting a gitweb description for a wildcard-matched repo
|
|
||||||
|
|
||||||
Similar to the getperm/setperm commands, there are the getdesc/setdesc
|
|
||||||
commands, thanks to Teemu.
|
|
||||||
|
|
||||||
### reporting
|
|
||||||
|
|
||||||
Remember the cool stuff you see when you just do `ssh git@server` (grep for
|
|
||||||
"myrights" in `doc/3-faq-tips-etc.mkd` if you forgot, or go [here][mr]).
|
|
||||||
|
|
||||||
[mr]: http://github.com/sitaramc/gitolite/blob/pu/doc/3-faq-tips-etc.mkd#myrights
|
|
||||||
|
|
||||||
This still works, except the format is a little more compressed to accommodate
|
|
||||||
a new column (at the start) for "C" permissions, which indicate that you are
|
|
||||||
allowed to *create* repos matching that pattern.
|
|
||||||
|
|
||||||
In addition, there is also the "expand" command, which takes any regex pattern
|
|
||||||
and returns you a list of all wildcard-created repos that you have access to
|
|
||||||
which fit that pattern.
|
|
||||||
|
|
||||||
### other issues and discussion
|
|
||||||
|
|
||||||
* *what if the repo name being pushed matches more than one pattern*?
|
|
||||||
|
|
||||||
I think it would be very hard to reason about access if we were to do
|
|
||||||
something like combine all the access rights in all the matching patterns.
|
|
||||||
No matter how you do it, and how carefully you document it, there'll be
|
|
||||||
someone who is surprised by the result.
|
|
||||||
|
|
||||||
And in security, that's a ***Bad Thing***.
|
|
||||||
|
|
||||||
So we don't combine permissions. At runtime, we die if we find more than
|
|
||||||
one match. Let 'em go holler at the admin for creating multiple matching
|
|
||||||
repo patterns :-)
|
|
||||||
|
|
||||||
This can make some repos inaccessible if the patterns changed *after* they
|
|
||||||
were created. The administrator should be careful not to do this. Most
|
|
||||||
of the time, it won't be difficult; the fixed prefix will usually be
|
|
||||||
different anyway so there won't be overlaps.
|
|
||||||
|
|
||||||
----
|
|
||||||
|
|
||||||
Enjoy, and please use with care. This is pretty powerful stuff. As they say:
|
|
||||||
if you break it, you get to keep both pieces :)
|
|
||||||
|
|
||||||
[jwzq]: http://regex.info/blog/2006-09-15/247
|
|
||||||
|
|
||||||
[av]: http://en.wikipedia.org/wiki/Autovivification
|
|
|
@ -1,99 +0,0 @@
|
||||||
# delegating access control responsibilities
|
|
||||||
|
|
||||||
[Thanks to jeromeag for forcing me to think through this...]
|
|
||||||
|
|
||||||
### 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. (The example in [this
|
|
||||||
section][ss] should give you an idea). As you can see, 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.
|
|
||||||
|
|
||||||
[ss]: http://github.com/sitaramc/gitolite/blob/pu/doc/3-faq-tips-etc.mkd#simpler_syntax
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
### 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
|
|
||||||
|
|
||||||
### 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.
|
|
|
@ -1,417 +0,0 @@
|
||||||
# ssh troubleshooting
|
|
||||||
|
|
||||||
In this document:
|
|
||||||
|
|
||||||
* basic ssh troubleshooting
|
|
||||||
* passphrases versus passwords
|
|
||||||
* ssh-agent problems
|
|
||||||
* basic ssh troubleshooting for the main admin
|
|
||||||
* basic ssh troubleshooting for a normal user
|
|
||||||
* details
|
|
||||||
* files on the server
|
|
||||||
* files on client
|
|
||||||
* why two keys on client
|
|
||||||
* more complex ssh setups
|
|
||||||
* two gitolite servers to manage?
|
|
||||||
* giving shell access to gitolite users
|
|
||||||
|
|
||||||
----
|
|
||||||
|
|
||||||
This document should help you troubleshoot ssh-related problems in accessing
|
|
||||||
gitolite *after* the install has completed successfully.
|
|
||||||
|
|
||||||
In addition, I **strongly** recommend reading [this document][glb] -- it's 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.
|
|
||||||
|
|
||||||
In addition to both these documents, there's now 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.
|
|
||||||
|
|
||||||
Please also note that ssh problems don't always look like ssh problems. One
|
|
||||||
common example: when the remote says the repo you're trying to access "does
|
|
||||||
not appear to be a git repository", and yet you are sure it exists, you
|
|
||||||
haven't mis-spelled it, etc. Another example is being able to access
|
|
||||||
repositories using the full unix path (typically like
|
|
||||||
`git@server:repositories/reponame.git`, assuming default `$REPO_BASE` setting,
|
|
||||||
instead of specifying only the part below `$REPO_BASE`, i.e.,
|
|
||||||
`git@server:reponame.git`).
|
|
||||||
|
|
||||||
[Both these errors indicate that you managed to bypass gitolite completely and
|
|
||||||
are using your shell access -- instead of running via
|
|
||||||
`/some/path/gl-auth-command <your_username>` it is just going to bash and
|
|
||||||
working from there!]
|
|
||||||
|
|
||||||
<a name="basic"></a>
|
|
||||||
|
|
||||||
### basic ssh troubleshooting
|
|
||||||
|
|
||||||
[glb]: http://sitaramc.github.com/0-installing/9-gitolite-basics.html#IMPORTANT_overview_of_ssh
|
|
||||||
|
|
||||||
I assume the gitolite server is called "server" and the user hosting all the
|
|
||||||
gitolite repos is "git". I will also be using "sitaram" as the *gitolite
|
|
||||||
username* of the admin.
|
|
||||||
|
|
||||||
Unless specifically mentioned, all these commands are run on the user's or
|
|
||||||
admin's workstation, not on the server.
|
|
||||||
|
|
||||||
#### passphrases versus passwords
|
|
||||||
|
|
||||||
When you create an ssh keypair, 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.
|
|
||||||
|
|
||||||
Do not confuse or mistake this prompt (`Enter passphrase for key
|
|
||||||
'/home/sitaram/.ssh/id_rsa':`) for a password prompt from the remote server!
|
|
||||||
|
|
||||||
You have two choices to avoid this prompt every time you try to access the
|
|
||||||
remote. 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 the next
|
|
||||||
section, further discussion of this is out of scope of this document.
|
|
||||||
|
|
||||||
#### ssh-agent problems
|
|
||||||
|
|
||||||
1. Run `ssh-add -l`. If this responds with either "The agent has no
|
|
||||||
identities." or "Could not open a connection to your authentication
|
|
||||||
agent.", skip this section.
|
|
||||||
|
|
||||||
2. However, if it lists some keys, like this:
|
|
||||||
|
|
||||||
2048 fc:c1:48:1e:06:31:97:a4:8b:fc:37:b2:76:14:c7:53 /home/sitaram/.ssh/id_rsa (RSA)
|
|
||||||
2048 d2:e0:7f:fa:1a:89:22:41:bb:06:d9:ff:a7:27:36:5c /home/sitaram/.ssh/sitaram (RSA)
|
|
||||||
|
|
||||||
then run `ls ~/.ssh` and make sure that all the keypairs you have there
|
|
||||||
are represented in the `ssh-add -l` output.
|
|
||||||
|
|
||||||
3. If you find any keypairs in `~/.ssh` that are not represented in the
|
|
||||||
`ssh-add -l` output, add them. For instance, if `ssh-add -l` showed me
|
|
||||||
only the `id_rsa` key, but I also had a `sitaram` (and `sitaram.pub`)
|
|
||||||
keypair, I'd run `ssh-add ~/.ssh/sitaram` to add it.
|
|
||||||
|
|
||||||
This is because ssh-agent has a quirk: if `ssh-add -l` shows *any* keys at
|
|
||||||
all, ssh will only use those keys. Even if you explicitly specify an unlisted
|
|
||||||
key using `ssh -i` or an `identityfile` directive in the config file, it won't
|
|
||||||
use it.
|
|
||||||
|
|
||||||
#### basic ssh troubleshooting for the main admin
|
|
||||||
|
|
||||||
You're the "main admin" if you're trying to access gitolite from the same
|
|
||||||
workstation and user account where you ran the "easy install" command. You
|
|
||||||
should have two keypairs in your `~/.ssh` directory. The pair called `id_rsa`
|
|
||||||
(and `id_rsa.pub`) was probably the first one you created, and you used this
|
|
||||||
to get passwordless (pubkey based) access to the server (which was a
|
|
||||||
pre-requisite for running the easy install command).
|
|
||||||
|
|
||||||
The second keypair has the same name as the last argument in the easy install
|
|
||||||
command you ran (in my case, `sitaram` and `sitaram.pub`). It was probably
|
|
||||||
created by the easy install script, and is the key used for gitolite access.
|
|
||||||
|
|
||||||
In addition, you should have a "gitolite" paragraph in your `~/.ssh/config`,
|
|
||||||
looking something like this:
|
|
||||||
|
|
||||||
host gitolite
|
|
||||||
user git
|
|
||||||
hostname server
|
|
||||||
identityfile ~/.ssh/sitaram
|
|
||||||
|
|
||||||
If any of these are not true, you did something funky in your install; email
|
|
||||||
me or hop onto #git and hope for the best ;-)
|
|
||||||
|
|
||||||
Otherwise, run these checks:
|
|
||||||
|
|
||||||
1. `ssh git@server` should get you a command line.
|
|
||||||
|
|
||||||
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 [gitolite version and access info][myrights], 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][myrights]. 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.
|
|
||||||
|
|
||||||
#### basic ssh troubleshooting for a normal user
|
|
||||||
|
|
||||||
For a normal user, life is much simpler. They should have only one pubkey,
|
|
||||||
which was previously sent to the gitolite admin to add into the admin repo's
|
|
||||||
`keydir` as "user.pub", and then "user" given permissions to some repo.
|
|
||||||
|
|
||||||
`ssh git@server info` should get you [gitolite version and access
|
|
||||||
info][myrights]. If it asks you for a password, your pubkey was not sent to
|
|
||||||
the server properly. Check with your admin.
|
|
||||||
|
|
||||||
[myrights]: http://github.com/sitaramc/gitolite/blob/pu/doc/3-faq-tips-etc.mkd#myrights
|
|
||||||
|
|
||||||
If it gets you the GNU info command output, you have shell access. This means
|
|
||||||
you had command line access to the server *before* you were added as a
|
|
||||||
gitolite user. If you send that same key to your gitolite admin to include in
|
|
||||||
the admin repo, it won't work. For reasons why, see below.
|
|
||||||
|
|
||||||
<a name="details"></a>
|
|
||||||
|
|
||||||
### details
|
|
||||||
|
|
||||||
Here's how it all hangs together.
|
|
||||||
|
|
||||||
#### 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").
|
|
||||||
|
|
||||||
#### 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:
|
|
||||||
|
|
||||||
~/.ssh/config
|
|
||||||
|
|
||||||
To understand why we need that, let's step back a bit. Normally, you
|
|
||||||
might expect to access gitolite repos like this:
|
|
||||||
|
|
||||||
ssh://git@server/reponame.git
|
|
||||||
|
|
||||||
But this won't work, because this ends up using the *default* keypair
|
|
||||||
(normally), which gives you a command line. Which means it won't invoke
|
|
||||||
the `gl-auth-command` program at all, and so none of gitolite's access
|
|
||||||
control will work.
|
|
||||||
|
|
||||||
<a name="altkey"></a>
|
|
||||||
|
|
||||||
You need to force ssh to use the *other* keypair when performing a git
|
|
||||||
operation. With normal ssh, that would be
|
|
||||||
|
|
||||||
ssh -i ~/.ssh/sitaram git@server
|
|
||||||
|
|
||||||
but git does not support putting an alternate keypair in the URL.
|
|
||||||
|
|
||||||
Luckily, ssh has a very convenient way of capturing all the connection
|
|
||||||
information (username, hostname, port number (if it's not the default 22),
|
|
||||||
and keypair to be used) in one "paragraph" of `~/.ssh/config`. This is
|
|
||||||
what the para looks like for us (the easy install script puts it there the
|
|
||||||
first time):
|
|
||||||
|
|
||||||
host gitolite
|
|
||||||
user git
|
|
||||||
hostname server
|
|
||||||
identityfile ~/.ssh/sitaram
|
|
||||||
|
|
||||||
(The "gitolite" can be anything you want of course; it's like a group name
|
|
||||||
for all the stuff below it). This ensures that typing
|
|
||||||
|
|
||||||
ssh gitolite
|
|
||||||
|
|
||||||
is equivalent to
|
|
||||||
|
|
||||||
ssh -i ~/.ssh/sitaram git@server
|
|
||||||
|
|
||||||
and therefore this:
|
|
||||||
|
|
||||||
git clone gitolite:reponame.git
|
|
||||||
|
|
||||||
now works as expected, invoking the special keypair instead of the default
|
|
||||||
one.
|
|
||||||
|
|
||||||
<a name="twokeys"></a>
|
|
||||||
|
|
||||||
#### why two keys on client
|
|
||||||
|
|
||||||
Why do I (the admin) need two **different** keypairs?
|
|
||||||
|
|
||||||
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, 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 really, really, want to get rid of the extra key, here's a transcript
|
|
||||||
that should have enough info to get you going (but it helps to know ssh well):
|
|
||||||
|
|
||||||
* on "sitaram" user, on my workstation
|
|
||||||
|
|
||||||
cd ~/.ssh
|
|
||||||
cp id_rsa sitaram
|
|
||||||
cp id_rsa.pub sitaram.pub
|
|
||||||
cd ~/gitolite-clone
|
|
||||||
src/gl-easy-install -q git my.git.server sitaram
|
|
||||||
|
|
||||||
that last command produces something like the following:
|
|
||||||
|
|
||||||
you are upgrading from (unknown) to v0.80-6-gdde8c4e
|
|
||||||
setting up keypair...
|
|
||||||
...reusing /home/sitaram/.ssh/sitaram.pub...
|
|
||||||
creating gitolite para in ~/.ssh/config...
|
|
||||||
finding/creating gitolite rc...
|
|
||||||
installing/upgrading...
|
|
||||||
Pseudo-terminal will not be allocated because stdin is not a terminal.
|
|
||||||
[master (root-commit) e717a89] start
|
|
||||||
2 files changed, 11 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/sitaram/gitolite-admin/.git/
|
|
||||||
fatal: 'gitolite-admin.git' does not appear to be a git repository
|
|
||||||
fatal: The remote end hung up unexpectedly
|
|
||||||
|
|
||||||
notice that the final step (the clone of the newly created gitolite-admin
|
|
||||||
repo) failed, as expected
|
|
||||||
|
|
||||||
* now log on to the git hosting account (`git@my.git.server` in this
|
|
||||||
example), edit `~/.ssh/authorized_keys`, and delete the line with the
|
|
||||||
first occurrence of your key (this should be *before* the `# gitolite
|
|
||||||
start` line)
|
|
||||||
|
|
||||||
* now go back to your workstation and
|
|
||||||
|
|
||||||
git clone git@my.git.server:gitolite-admin
|
|
||||||
|
|
||||||
That should do it.
|
|
||||||
|
|
||||||
<a name="complex"></a>
|
|
||||||
|
|
||||||
### more complex ssh setups
|
|
||||||
|
|
||||||
What do you need to know in order to create more complex ssh setups (for
|
|
||||||
instance if you have *two* gitolite servers you are administering)?
|
|
||||||
|
|
||||||
#### two gitolite servers to manage?
|
|
||||||
|
|
||||||
* they can have the same key; no harm there (example, sitaram.pub)
|
|
||||||
|
|
||||||
* instead of just one ssh/config para, you now have two (assuming that the
|
|
||||||
remote user on both machines is called "git"):
|
|
||||||
|
|
||||||
host gitolite
|
|
||||||
user git
|
|
||||||
hostname server
|
|
||||||
identityfile ~/.ssh/sitaram
|
|
||||||
|
|
||||||
host gitolite2
|
|
||||||
user git
|
|
||||||
hostname server2
|
|
||||||
identityfile ~/.ssh/sitaram
|
|
||||||
|
|
||||||
* now access one server's repos as `gitolite:reponame.git` and the other
|
|
||||||
server's repos as `gitolite2:reponame.git`.
|
|
||||||
|
|
||||||
<a name="shell"></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:
|
|
||||||
|
|
||||||
* (**on the server**) listing all such users in a variable called
|
|
||||||
`$SHELL_USERS` in the `~/.gitolite.rc` file. For example:
|
|
||||||
|
|
||||||
$SHELL_USERS = "alice bob";
|
|
||||||
|
|
||||||
(Note the syntax: a space separated list of users in one string variable).
|
|
||||||
|
|
||||||
* (**on your client**) make at least a dummy change to your clone of the
|
|
||||||
gitolite-admin repo and push it.
|
|
||||||
|
|
||||||
**IMPORTANT UPGRADE NOTE**: a previous implementation of this feature worked
|
|
||||||
by adding people to a special group (`@SHELL`) in the *config* file. This
|
|
||||||
meant that anyone with gitolite-admin repo write access could add himself to
|
|
||||||
the `@SHELL` group and push, thus obtaining shell.
|
|
||||||
|
|
||||||
This is not a problem for most setups, but if someone wants to separate these
|
|
||||||
two privileges (the right to push the admin repo and the right to get a shell)
|
|
||||||
then it does pose a problem. Since the "rc" file can only be edited by
|
|
||||||
someone who already has shell access, we now use that instead, even though
|
|
||||||
this forces a change in the syntax.
|
|
||||||
|
|
||||||
To migrate from the old scheme to the new one, add a new variable
|
|
||||||
`$SHELL_USERS` to `~/.gitolite.rc` on the server with the appropriate names in
|
|
||||||
it. **It is best to do this directly on the server *before* upgrading to this
|
|
||||||
version.** (After the upgrade is done and tested you can remove the `@SHELL`
|
|
||||||
lines from the gitolite config file).
|
|
||||||
|
|
|
@ -1,211 +0,0 @@
|
||||||
# gitolite install transcript
|
|
||||||
|
|
||||||
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. 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 :)
|
|
||||||
|
|
||||||
----
|
|
||||||
|
|
||||||
### 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 ]
|
|
||||||
|
|
||||||
----
|
|
||||||
|
|
||||||
### 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
|
|
||||||
|
|
||||||
----
|
|
||||||
|
|
||||||
### 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.
|
|
||||||
|
|
||||||
### 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 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.
|
|
||||||
|
|
||||||
----
|
|
||||||
|
|
||||||
### examine what you have
|
|
||||||
|
|
||||||
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. Try out that
|
|
||||||
`tail -31 ./gl-easy-install` too :)
|
|
|
@ -1,48 +0,0 @@
|
||||||
Major changes to gitolite, master branch only, most recent first, no dates but
|
|
||||||
the tags can help you position stuff approximately
|
|
||||||
|
|
||||||
- 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
|
|
3
doc/TODO
3
doc/TODO
|
@ -1,3 +0,0 @@
|
||||||
* make a proper test suite
|
|
||||||
|
|
||||||
* change the "rc" file to use "gitconfig" instead...
|
|
|
@ -1,158 +0,0 @@
|
||||||
## Gitolite ##
|
|
||||||
|
|
||||||
Git has started to become very popular in corporate environments, which tend to have some additional requirements in terms of access control. Gitolite was created to help with those requirements.
|
|
||||||
|
|
||||||
Gitolite allows you to specify permissions not just by repository (like Gitosis does), but also by branch or tag names within each repository. That is, you can specify that certain people (or groups of people) can only push certain "refs" (branches or tags) but not others.
|
|
||||||
|
|
||||||
### Installing ###
|
|
||||||
|
|
||||||
Installing Gitolite is very easy, even if you don't read the extensive documentation that comes with it. You need an account on a Unix server of some kind; various Linux flavours, and Solaris 10, have been tested. You do not need root access, assuming git, perl, and an openssh compatible ssh server are already installed. In the examples below, we will use the `gitolite` account on a host called `gitserver`.
|
|
||||||
|
|
||||||
Curiously, Gitolite is installed by running a script *on the workstation*, so your workstation must have a bash shell available. Even the bash that comes with msysgit will do, in case you're wondering.
|
|
||||||
|
|
||||||
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 (just like Gitosis).
|
|
||||||
|
|
||||||
[By the way, *upgrading* gitolite is also done the same way. Also, if you're interested, run the script without any arguments to get a usage message.]
|
|
||||||
|
|
||||||
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*!)
|
|
||||||
|
|
||||||
### 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.
|
|
||||||
|
|
||||||
### Config File and Access Control Rules ###
|
|
||||||
|
|
||||||
So 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 *quite* different from Gitosis. Again, this 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. This is the only access control that Gitosis had.
|
|
||||||
|
|
||||||
The second level, applicable only to "write" access, is by branch or tag within a repository. The username, the access being attempted (`W` or `+`), and the refname being updated are known. The access rules are checked in order of appearance in the config file, looking for a match for this combination (but remember that the refname is regex-matched, not merely string-matched). If a match is found, the push succeeds. A fallthrough results in access being denied.
|
|
||||||
|
|
||||||
### Advanced Access Control with "deny" rules ###
|
|
||||||
|
|
||||||
So far, we've only seen permissions to be one or `R`, `RW`, or `RW+`. However, gitolite allows another permission: `-`, standing for "deny". This gives you a lot more power, at the expense of some complexity, because now fallthrough is not the *only* way for access to be denied, so the *order of the rules now matters*!
|
|
||||||
|
|
||||||
Let us say, in the situation above, we want engineers to be able to rewind any branch *except* master and integ. Here's how to do that:
|
|
||||||
|
|
||||||
RW master integ = @engineers
|
|
||||||
- master integ = @engineers
|
|
||||||
RW+ = @engineers
|
|
||||||
|
|
||||||
Again, you simply follow the rules top down until you hit a match for your access mode, or a deny. Non-rewind push to master or integ is allowed by the first rule. A rewind push to those refs does not match the first rule, drops down to the second, and is therefore denied. Any push (rewind or non-rewind) to refs other than master or integ won't match the first two rules anyway, and the third rule allows it.
|
|
||||||
|
|
||||||
### Restricting pushes by files changed ###
|
|
||||||
|
|
||||||
In addition to restricting what branches a user can push changes to, you can also restrict what files they are allowed to touch. For example, perhaps the Makefile (or some other program) is really not supposed to be changed by just anyone, because a lot of things depend on it or would break if the changes are not done *just right*. You can tell gitolite:
|
|
||||||
|
|
||||||
repo foo
|
|
||||||
RW = @junior_devs @senior_devs
|
|
||||||
|
|
||||||
RW NAME/ = @senior_devs
|
|
||||||
- NAME/Makefile = @junior_devs
|
|
||||||
RW NAME/ = @junior_devs
|
|
||||||
|
|
||||||
This powerful feature is documented in `conf/example.conf`.
|
|
||||||
|
|
||||||
### Personal Branches ###
|
|
||||||
|
|
||||||
Gitolite also has a feature called "personal branches" (or rather, "personal branch namespace") that can be very useful in a corporate environment.
|
|
||||||
|
|
||||||
A lot of code exchange in the git world happens by "please pull" requests. In a corporate environment, however, unauthenticated access is a no-no, and a developer workstation cannot do authentication, so you have to push to the central server and ask someone to pull from there.
|
|
||||||
|
|
||||||
This would normally cause the same branch name clutter as in a centralised VCS, plus setting up permissions for this becomes a chore for the admin.
|
|
||||||
|
|
||||||
Gitolite lets you define a "personal" or "scratch" namespace prefix for each developer (for example, `refs/personal/<devname>/*`), with full permissions for that dev only, and read access for everyone else. Just choose a verbose install and set the `$PERSONAL` variable in the "rc" file to `refs/personal`. That's all; it's pretty much fire and forget as far as the admin is concerned, even if there is constant churn in the project team composition.
|
|
||||||
|
|
||||||
### "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/4-wildcard-repositories.mkd`.
|
|
||||||
|
|
||||||
### Other Features ###
|
|
||||||
|
|
||||||
We'll round off this discussion with a bunch of other features, all of which are described in great detail in the "faqs, tips, etc" document.
|
|
||||||
|
|
||||||
**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. Older versions of gitolite used to complain about the `SSH_ORIGINAL_COMMAND` environment variable being empty (see the ssh documentation if interested). Now Gitolite comes up with something like this:
|
|
||||||
|
|
||||||
hello sitaram, the gitolite version here is v0.90-9-g91e1e9f
|
|
||||||
you have the following permissions:
|
|
||||||
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.
|
|
|
@ -1,113 +0,0 @@
|
||||||
#!/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, $PERSONAL);
|
|
||||||
our %repos;
|
|
||||||
|
|
||||||
# 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};
|
|
||||||
# then "do" the compiled config file, whose name we now know. Before doing
|
|
||||||
# that we setup the creater etc from environment variables so that the parse
|
|
||||||
# interpolates them. We've minimised the duplication but this *does*
|
|
||||||
# duplicate a bit of parse_acl from gitolite.pm; we don't want to include that
|
|
||||||
# file here just for that little bit
|
|
||||||
{
|
|
||||||
our $creater = $ENV{GL_CREATER};
|
|
||||||
our $readers = $ENV{GL_READERS};
|
|
||||||
our $writers = $ENV{GL_WRITERS};
|
|
||||||
|
|
||||||
die "parse $GL_CONF_COMPILED failed: " . ($! or $@) unless do $GL_CONF_COMPILED;
|
|
||||||
|
|
||||||
$repos{$ENV{GL_REPO}} = $repos{$ENV{GL_REPOPATT}} if ( $ENV{GL_REPOPATT} );
|
|
||||||
}
|
|
||||||
my $reported_repo = $ENV{GL_REPO} . ( $ENV{GL_REPOPATT} ? " ($ENV{GL_REPOPATT})" : "" );
|
|
||||||
|
|
||||||
# we've started to need some common subs in what used to be a small, cute,
|
|
||||||
# little script that barely spanned a few lines :(
|
|
||||||
require "$ENV{GL_BINDIR}/gitolite.pm";
|
|
||||||
|
|
||||||
# ----------------------------------------------------------------------------
|
|
||||||
# start...
|
|
||||||
# ----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
# what are you trying to do? (is it 'W' or '+'?)
|
|
||||||
my $perm = 'W';
|
|
||||||
# rewriting a tag is considered a rewind, in terms of permissions
|
|
||||||
$perm = '+' 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
|
|
||||||
$perm = '+' if $oldsha ne $merge_base;
|
|
||||||
|
|
||||||
my @allowed_refs;
|
|
||||||
# personal stuff -- right at the start in the new regime, I guess!
|
|
||||||
push @allowed_refs, { "$PERSONAL/$ENV{GL_USER}/" => "RW+" } if $PERSONAL;
|
|
||||||
# we want specific perms to override @all, so they come first
|
|
||||||
push @allowed_refs, @ { $repos{$ENV{GL_REPO}}{$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
|
|
||||||
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), $perm);
|
|
||||||
&check_ref (\@allowed_refs, $ENV{GL_REPO}, $_ , $perm) for @refs;
|
|
||||||
|
|
||||||
# if we returned at all, all the checks succeeded, so we log the action and exit 0
|
|
||||||
|
|
||||||
&log_it("$ENV{GL_TS} $perm\t" .
|
|
||||||
substr($oldsha, 0, 14) . "\t" . substr($newsha, 0, 14) .
|
|
||||||
"\t$reported_repo\t$ref\t$ENV{GL_USER}\t$log_refex\n");
|
|
||||||
exit 0;
|
|
|
@ -1,8 +0,0 @@
|
||||||
#!/bin/sh
|
|
||||||
|
|
||||||
# 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
|
|
||||||
|
|
||||||
cd $GL_ADMINDIR
|
|
||||||
$GL_BINDIR/gl-compile-conf
|
|
77
install
Executable file
77
install
Executable file
|
@ -0,0 +1,77 @@
|
||||||
|
#!/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 );
|
||||||
|
}
|
51
src/VREF/COUNT
Executable file
51
src/VREF/COUNT
Executable file
|
@ -0,0 +1,51 @@
|
||||||
|
#!/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
|
66
src/VREF/EMAIL-CHECK
Executable file
66
src/VREF/EMAIL-CHECK
Executable file
|
@ -0,0 +1,66 @@
|
||||||
|
#!/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
|
||||||
|
|
45
src/VREF/FILETYPE
Executable file
45
src/VREF/FILETYPE
Executable file
|
@ -0,0 +1,45 @@
|
||||||
|
#!/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
|
||||||
|
}
|
40
src/VREF/MAX_NEWBIN_SIZE
Executable file
40
src/VREF/MAX_NEWBIN_SIZE
Executable file
|
@ -0,0 +1,40 @@
|
||||||
|
#!/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
|
49
src/VREF/MERGE-CHECK
Normal file
49
src/VREF/MERGE-CHECK
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
#!/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;
|
80
src/VREF/VOTES
Executable file
80
src/VREF/VOTES
Executable file
|
@ -0,0 +1,80 @@
|
||||||
|
#!/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
|
36
src/VREF/lock
Executable file
36
src/VREF/lock
Executable file
|
@ -0,0 +1,36 @@
|
||||||
|
#!/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
|
41
src/VREF/partial-copy
Executable file
41
src/VREF/partial-copy
Executable file
|
@ -0,0 +1,41 @@
|
||||||
|
#!/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
|
75
src/VREF/refex-expr
Executable file
75
src/VREF/refex-expr
Executable file
|
@ -0,0 +1,75 @@
|
||||||
|
#!/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.
|
131
src/commands/D
Executable file
131
src/commands/D
Executable file
|
@ -0,0 +1,131 @@
|
||||||
|
#!/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
|
74
src/commands/access
Executable file
74
src/commands/access
Executable file
|
@ -0,0 +1,74 @@
|
||||||
|
#!/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";
|
||||||
|
}
|
15
src/commands/create
Executable file
15
src/commands/create
Executable file
|
@ -0,0 +1,15 @@
|
||||||
|
#!/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
|
40
src/commands/creator
Executable file
40
src/commands/creator
Executable file
|
@ -0,0 +1,40 @@
|
||||||
|
#!/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";
|
43
src/commands/desc
Executable file
43
src/commands/desc
Executable file
|
@ -0,0 +1,43 @@
|
||||||
|
#!/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"
|
59
src/commands/fork
Executable file
59
src/commands/fork
Executable file
|
@ -0,0 +1,59 @@
|
||||||
|
#!/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
|
86
src/commands/git-config
Executable file
86
src/commands/git-config
Executable file
|
@ -0,0 +1,86 @@
|
||||||
|
#!/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;
|
||||||
|
}
|
42
src/commands/help
Executable file
42
src/commands/help
Executable file
|
@ -0,0 +1,42 @@
|
||||||
|
#!/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`;
|
||||||
|
}
|
44
src/commands/htpasswd
Executable file
44
src/commands/htpasswd
Executable file
|
@ -0,0 +1,44 @@
|
||||||
|
#!/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;
|
107
src/commands/info
Executable file
107
src/commands/info
Executable file
|
@ -0,0 +1,107 @@
|
||||||
|
#!/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";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
55
src/commands/list-dangling-repos
Executable file
55
src/commands/list-dangling-repos
Executable file
|
@ -0,0 +1,55 @@
|
||||||
|
#!/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";
|
124
src/commands/lock
Executable file
124
src/commands/lock
Executable file
|
@ -0,0 +1,124 @@
|
||||||
|
#!/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 );
|
||||||
|
}
|
77
src/commands/mirror
Executable file
77
src/commands/mirror
Executable file
|
@ -0,0 +1,77 @@
|
||||||
|
#!/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};
|
||||||
|
}
|
127
src/commands/perms
Executable file
127
src/commands/perms
Executable file
|
@ -0,0 +1,127 @@
|
||||||
|
#!/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 );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
8
src/commands/print-default-rc
Executable file
8
src/commands/print-default-rc
Executable file
|
@ -0,0 +1,8 @@
|
||||||
|
#!/usr/bin/perl
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
use lib $ENV{GL_LIBDIR};
|
||||||
|
use Gitolite::Rc;
|
||||||
|
|
||||||
|
print glrc('default-text');
|
5
src/commands/push
Executable file
5
src/commands/push
Executable file
|
@ -0,0 +1,5 @@
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
export GL_BYPASS_ACCESS_CHECKS=1
|
||||||
|
|
||||||
|
git push "$@"
|
149
src/commands/rsync
Executable file
149
src/commands/rsync
Executable file
|
@ -0,0 +1,149 @@
|
||||||
|
#!/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(@_);
|
||||||
|
}
|
192
src/commands/sshkeys-lint
Executable file
192
src/commands/sshkeys-lint
Executable file
|
@ -0,0 +1,192 @@
|
||||||
|
#!/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;
|
||||||
|
}
|
280
src/commands/sskm
Executable file
280
src/commands/sskm
Executable file
|
@ -0,0 +1,280 @@
|
||||||
|
#!/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";
|
||||||
|
}
|
24
src/commands/sudo
Executable file
24
src/commands/sudo
Executable file
|
@ -0,0 +1,24 @@
|
||||||
|
#!/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 "$@"
|
17
src/commands/svnserve
Executable file
17
src/commands/svnserve
Executable file
|
@ -0,0 +1,17 @@
|
||||||
|
#!/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";
|
31
src/commands/symbolic-ref
Executable file
31
src/commands/symbolic-ref
Executable file
|
@ -0,0 +1,31 @@
|
||||||
|
#!/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 "$@"
|
57
src/commands/who-pushed
Executable file
57
src/commands/who-pushed
Executable file
|
@ -0,0 +1,57 @@
|
||||||
|
#!/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'");
|
||||||
|
}
|
||||||
|
}
|
56
src/commands/writable
Executable file
56
src/commands/writable
Executable file
|
@ -0,0 +1,56 @@
|
||||||
|
#!/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 );
|
||||||
|
}
|
||||||
|
}
|
102
src/gitolite
Executable file
102
src/gitolite
Executable file
|
@ -0,0 +1,102 @@
|
||||||
|
#!/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';
|
||||||
|
}
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------
|
239
src/gitolite-shell
Executable file
239
src/gitolite-shell
Executable file
|
@ -0,0 +1,239 @@
|
||||||
|
#!/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";
|
||||||
|
}
|
472
src/gitolite.pm
472
src/gitolite.pm
|
@ -1,472 +0,0 @@
|
||||||
use strict;
|
|
||||||
# this file is commonly used using "require". It is not required to use "use"
|
|
||||||
# (because it doesn't live in a different package)
|
|
||||||
|
|
||||||
# warning: preceding para requires 4th attribute of a programmer after
|
|
||||||
# laziness, impatience, and hubris: sense of humour :-)
|
|
||||||
|
|
||||||
# WARNING
|
|
||||||
# -------
|
|
||||||
# the name of this file will change as soon as its function/feature set
|
|
||||||
# stabilises enough ;-)
|
|
||||||
|
|
||||||
# right now all it does is
|
|
||||||
# - define a function that tells you where to find the rc file
|
|
||||||
# - define a function that creates a new repo and give it our update hook
|
|
||||||
|
|
||||||
# ----------------------------------------------------------------------------
|
|
||||||
# common definitions
|
|
||||||
# ----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
our $ABRT = "\n\t\t***** ABORTING *****\n ";
|
|
||||||
our $WARN = "\n\t\t***** WARNING *****\n ";
|
|
||||||
|
|
||||||
# commands we're expecting
|
|
||||||
our $R_COMMANDS=qr/^(git[ -]upload-pack|git[ -]upload-archive)$/;
|
|
||||||
our $W_COMMANDS=qr/^git[ -]receive-pack$/;
|
|
||||||
|
|
||||||
# note that REPONAME_PATT allows "/", while USERNAME_PATT does not
|
|
||||||
# also, the reason REPONAME_PATT is a superset of USERNAME_PATT is (duh!)
|
|
||||||
# because in this version, a repo can have "CREATER" in the name (see docs)
|
|
||||||
our $REPONAME_PATT=qr(^\@?[0-9a-zA-Z][0-9a-zA-Z._\@/+-]*$); # very simple pattern
|
|
||||||
our $USERNAME_PATT=qr(^\@?[0-9a-zA-Z][0-9a-zA-Z._\@+-]*$); # very simple pattern
|
|
||||||
# same as REPONAME, plus some common regex metas
|
|
||||||
our $REPOPATT_PATT=qr(^\@?[0-9a-zA-Z][\\^.$|()[\]*+?{}0-9a-zA-Z._\@/-]*$);
|
|
||||||
|
|
||||||
# these come from the RC file
|
|
||||||
our ($REPO_UMASK, $GL_WILDREPOS, $GL_PACKAGE_CONF, $GL_PACKAGE_HOOKS);
|
|
||||||
our %repos;
|
|
||||||
|
|
||||||
# ----------------------------------------------------------------------------
|
|
||||||
# convenience subs
|
|
||||||
# ----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
sub wrap_chdir {
|
|
||||||
chdir($_[0]) or die "$ABRT chdir $_[0] failed: $! at ", (caller)[1], " line ", (caller)[2], "\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
sub wrap_open {
|
|
||||||
open (my $fh, $_[0], $_[1]) or die "$ABRT open $_[1] failed: $! at ", (caller)[1], " line ", (caller)[2], "\n" .
|
|
||||||
( $_[2] || '' ); # suffix custom error message if given
|
|
||||||
return $fh;
|
|
||||||
}
|
|
||||||
|
|
||||||
sub log_it {
|
|
||||||
open my $log_fh, ">>", $ENV{GL_LOG} or die "open log failed: $!\n";
|
|
||||||
print $log_fh @_;
|
|
||||||
close $log_fh or die "close log failed: $!\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
# check one ref
|
|
||||||
sub check_ref {
|
|
||||||
|
|
||||||
# normally, the $ref will be whatever ref the commit is trying to update
|
|
||||||
# (like refs/heads/master or whatever). At least one of the refexes that
|
|
||||||
# pertain to this user must match this ref **and** the corresponding
|
|
||||||
# permission must also match the action (W or +) being attempted. If none
|
|
||||||
# of them match, the access is denied.
|
|
||||||
|
|
||||||
# Notice that the function DIES!!! Any future changes that require more
|
|
||||||
# work to be done *after* this, even on failure, can start using return
|
|
||||||
# codes etc., but for now we're happy to just die.
|
|
||||||
|
|
||||||
my ($allowed_refs, $repo, $ref, $perm) = @_;
|
|
||||||
for my $ar (@{$allowed_refs}) {
|
|
||||||
my $refex = (keys %$ar)[0];
|
|
||||||
# refex? sure -- a regex to match a ref against :)
|
|
||||||
next unless $ref =~ /^$refex/;
|
|
||||||
die "$perm $ref $ENV{GL_USER} DENIED by $refex\n" if $ar->{$refex} eq '-';
|
|
||||||
|
|
||||||
# as far as *this* ref is concerned we're ok
|
|
||||||
return $refex if ($ar->{$refex} =~ /\Q$perm/);
|
|
||||||
}
|
|
||||||
die "$perm $ref $repo $ENV{GL_USER} DENIED by fallthru\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
# ln -sf :-)
|
|
||||||
sub ln_sf
|
|
||||||
{
|
|
||||||
my($srcdir, $glob, $dstdir) = @_;
|
|
||||||
for my $hook ( glob("$srcdir/$glob") ) {
|
|
||||||
$hook =~ s/$srcdir\///;
|
|
||||||
unlink "$dstdir/$hook";
|
|
||||||
symlink "$srcdir/$hook", "$dstdir/$hook" or die "could not symlink $hook\n";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
# ----------------------------------------------------------------------------
|
|
||||||
# where is the rc file hiding?
|
|
||||||
# ----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
sub where_is_rc
|
|
||||||
{
|
|
||||||
# till now, the rc file was in one fixed place: .gitolite.rc in $HOME of
|
|
||||||
# the user hosting the gitolite repos. This was fine, because gitolite is
|
|
||||||
# all about empowering non-root users :-)
|
|
||||||
|
|
||||||
# then we wanted to make a debian package out of it (thank you, Rhonda!)
|
|
||||||
# which means (a) it's going to be installed by root anyway and (b) any
|
|
||||||
# config files have to be in /etc/<something>
|
|
||||||
|
|
||||||
# the only way to resolve this in a backward compat way is to look for the
|
|
||||||
# $HOME one, and if you don't find it look for the /etc one
|
|
||||||
|
|
||||||
# this common routine does that, setting an env var for the first one it
|
|
||||||
# finds
|
|
||||||
|
|
||||||
return if $ENV{GL_RC};
|
|
||||||
|
|
||||||
for my $glrc ( $ENV{HOME} . "/.gitolite.rc", "/etc/gitolite/gitolite.rc" ) {
|
|
||||||
if (-f $glrc) {
|
|
||||||
$ENV{GL_RC} = $glrc;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# ----------------------------------------------------------------------------
|
|
||||||
# create a new repository
|
|
||||||
# ----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
# NOTE: this sub will change your cwd; caller beware!
|
|
||||||
sub new_repo
|
|
||||||
{
|
|
||||||
my ($repo, $hooks_dir, $creater) = @_;
|
|
||||||
|
|
||||||
umask($REPO_UMASK);
|
|
||||||
die "wildrepos disabled, can't set creater $creater on new repo $repo\n"
|
|
||||||
if $creater and not $GL_WILDREPOS;
|
|
||||||
|
|
||||||
system("mkdir", "-p", "$repo.git") and die "$ABRT mkdir $repo.git failed: $!\n";
|
|
||||||
# erm, note that's "and die" not "or die" as is normal in perl
|
|
||||||
wrap_chdir("$repo.git");
|
|
||||||
system("git --bare init >&2");
|
|
||||||
if ($creater) {
|
|
||||||
system("echo $creater > gl-creater");
|
|
||||||
system("git", "config", "gitweb.owner", $creater);
|
|
||||||
}
|
|
||||||
# propagate our own, plus any local admin-defined, hooks
|
|
||||||
ln_sf($hooks_dir, "*", "hooks");
|
|
||||||
# in case of package install, GL_ADMINDIR is no longer the top cop;
|
|
||||||
# override with the package hooks
|
|
||||||
ln_sf("$GL_PACKAGE_HOOKS/common", "*", "hooks") if $GL_PACKAGE_HOOKS;
|
|
||||||
chmod 0755, "hooks/update";
|
|
||||||
}
|
|
||||||
|
|
||||||
# ----------------------------------------------------------------------------
|
|
||||||
# metaphysics (like, "is there a god?", "who created me?", etc)
|
|
||||||
# ----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
# "who created this repo", "am I on the R list", and "am I on the RW list"?
|
|
||||||
sub repo_rights
|
|
||||||
{
|
|
||||||
my ($repo_base_abs, $repo, $user) = @_;
|
|
||||||
# creater
|
|
||||||
my $c = '';
|
|
||||||
if ( -f "$repo_base_abs/$repo.git/gl-creater") {
|
|
||||||
my $fh = wrap_open("<", "$repo_base_abs/$repo.git/gl-creater");
|
|
||||||
chomp($c = <$fh>);
|
|
||||||
}
|
|
||||||
# $user's R and W rights
|
|
||||||
my ($r, $w); $r = ''; $w = '';
|
|
||||||
if ($user and -f "$repo_base_abs/$repo.git/gl-perms") {
|
|
||||||
my $fh = wrap_open("<", "$repo_base_abs/$repo.git/gl-perms");
|
|
||||||
my $perms = join ("", <$fh>);
|
|
||||||
if ($perms) {
|
|
||||||
$r = $user if $perms =~ /^\s*R(?=\s).*\s$user(\s|$)/m;
|
|
||||||
$w = $user if $perms =~ /^\s*RW(?=\s).*\s$user(\s|$)/m;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ($c, $r, $w);
|
|
||||||
}
|
|
||||||
|
|
||||||
# ----------------------------------------------------------------------------
|
|
||||||
# getperms and setperms
|
|
||||||
# ----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
sub get_set_perms
|
|
||||||
{
|
|
||||||
my($repo_base_abs, $repo, $verb, $user) = @_;
|
|
||||||
my ($creater, $dummy, $dummy2) = &repo_rights($repo_base_abs, $repo, "");
|
|
||||||
die "$repo doesnt exist or is not yours\n" unless $user eq $creater;
|
|
||||||
wrap_chdir("$repo_base_abs");
|
|
||||||
wrap_chdir("$repo.git");
|
|
||||||
if ($verb eq 'getperms') {
|
|
||||||
system("cat", "gl-perms") if -f "gl-perms";
|
|
||||||
} else {
|
|
||||||
system("cat > gl-perms");
|
|
||||||
print "New perms are:\n";
|
|
||||||
system("cat", "gl-perms");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# ----------------------------------------------------------------------------
|
|
||||||
# getdesc and setdesc
|
|
||||||
# ----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
sub get_set_desc
|
|
||||||
{
|
|
||||||
my($repo_base_abs, $repo, $verb, $user) = @_;
|
|
||||||
my ($creater, $dummy, $dummy2) = &repo_rights($repo_base_abs, $repo, "");
|
|
||||||
die "$repo doesnt exist or is not yours\n" unless $user eq $creater;
|
|
||||||
wrap_chdir("$repo_base_abs");
|
|
||||||
wrap_chdir("$repo.git");
|
|
||||||
if ($verb eq 'getdesc') {
|
|
||||||
system("cat", "description") if -f "description";
|
|
||||||
} else {
|
|
||||||
system("cat > description");
|
|
||||||
print "New description is:\n";
|
|
||||||
system("cat", "description");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# ----------------------------------------------------------------------------
|
|
||||||
# parse the compiled acl
|
|
||||||
# ----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
sub parse_acl
|
|
||||||
{
|
|
||||||
# IMPLEMENTATION NOTE: a wee bit of this is duplicated in the update hook;
|
|
||||||
# please update that also if the interface or the env vars change
|
|
||||||
|
|
||||||
my ($GL_CONF_COMPILED, $repo, $c, $r, $w) = @_;
|
|
||||||
$c = $r = $w = "NOBODY" unless $GL_WILDREPOS;
|
|
||||||
|
|
||||||
# void $r if same as $w (otherwise "readers" overrides "writers"; this is
|
|
||||||
# the same problem that needed a sort sub for the Dumper in the compile
|
|
||||||
# script, but in this case it's limited to just $readers and $writers)
|
|
||||||
$r = "NOBODY" if $r eq $w;
|
|
||||||
|
|
||||||
# set up the variables for a parse to interpolate stuff from the dumped
|
|
||||||
# hash (remember the selective conversion of single to double quotes?).
|
|
||||||
|
|
||||||
# if they're not passed in, then we look for an env var of that name, else
|
|
||||||
# we default to "NOBODY" (we hope there isn't a real user called NOBODY!)
|
|
||||||
# And in any case, we set those env vars so level 2 can redo the last
|
|
||||||
# parse without any special code
|
|
||||||
|
|
||||||
our $creater = $ENV{GL_CREATER} = $c || $ENV{GL_CREATER} || "NOBODY";
|
|
||||||
our $readers = $ENV{GL_READERS} = $r || $ENV{GL_READERS} || "NOBODY";
|
|
||||||
our $writers = $ENV{GL_WRITERS} = $w || $ENV{GL_WRITERS} || "NOBODY";
|
|
||||||
|
|
||||||
die "parse $GL_CONF_COMPILED failed: " . ($! or $@) unless do $GL_CONF_COMPILED;
|
|
||||||
|
|
||||||
# basic access reporting doesn't send $repo, and doesn't need to; you just
|
|
||||||
# want the config dumped as is, really
|
|
||||||
return unless $repo;
|
|
||||||
|
|
||||||
return $ENV{GL_REPOPATT} = "" if $repos{$repo};
|
|
||||||
# didn't find it, but wild is off? too bad, die!!! muahahaha
|
|
||||||
die "$repo not found in compiled config\n" unless $GL_WILDREPOS;
|
|
||||||
|
|
||||||
# didn't find $repo in %repos, so it must be a wildcard-match case
|
|
||||||
my @matched = grep { $repo =~ /^$_$/ } sort keys %repos;
|
|
||||||
die "$repo has no matches\n" unless @matched;
|
|
||||||
die "$repo has multiple matches\n@matched\n" if @matched > 1;
|
|
||||||
# found exactly one pattern that matched, copy its ACL
|
|
||||||
$repos{$repo} = $repos{$matched[0]};
|
|
||||||
# and return the pattern
|
|
||||||
return $ENV{GL_REPOPATT} = $matched[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
# ----------------------------------------------------------------------------
|
|
||||||
# print a report of $user's basic permissions
|
|
||||||
# ----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
# basic means wildcards will be shown as wildcards; this is pretty much what
|
|
||||||
# got parsed by the compile script
|
|
||||||
sub report_basic
|
|
||||||
{
|
|
||||||
my($GL_ADMINDIR, $GL_CONF_COMPILED, $user) = @_;
|
|
||||||
|
|
||||||
&parse_acl($GL_CONF_COMPILED, "", "CREATER", "READERS", "WRITERS");
|
|
||||||
|
|
||||||
# send back some useful info if no command was given
|
|
||||||
print "hello $user, the gitolite version here is ";
|
|
||||||
system("cat", ($GL_PACKAGE_CONF || "$GL_ADMINDIR/conf") . "/VERSION");
|
|
||||||
print "\ryou have the following permissions:\r\n";
|
|
||||||
for my $r (sort keys %repos) {
|
|
||||||
my $perm .= ( $repos{$r}{C}{'@all'} ? ' @' : ( $repos{$r}{C}{$user} ? ' C' : ' ' ) );
|
|
||||||
$perm .= ( $repos{$r}{R}{'@all'} ? ' @' : ( $repos{$r}{R}{$user} ? ' R' : ' ' ) );
|
|
||||||
$perm .= ( $repos{$r}{W}{'@all'} ? ' @' : ( $repos{$r}{W}{$user} ? ' W' : ' ' ) );
|
|
||||||
print "$perm\t$r\r\n" if $perm =~ /\S/;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# ----------------------------------------------------------------------------
|
|
||||||
# print a report of $user's basic permissions
|
|
||||||
# ----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
sub expand_wild
|
|
||||||
{
|
|
||||||
my($GL_CONF_COMPILED, $repo_base_abs, $repo, $user) = @_;
|
|
||||||
|
|
||||||
# this is for convenience; he can copy-paste the output of the basic
|
|
||||||
# access report instead of having to manually change CREATER to his name
|
|
||||||
$repo =~ s/\bCREAT[EO]R\b/$user/g;
|
|
||||||
|
|
||||||
# get the list of repo patterns
|
|
||||||
&parse_acl($GL_CONF_COMPILED, "", "NOBODY", "NOBODY", "NOBODY");
|
|
||||||
my %normal_repos = %repos;
|
|
||||||
|
|
||||||
# display matching repos (from *all* the repos in the system) that $user
|
|
||||||
# has at least "R" access to
|
|
||||||
|
|
||||||
chdir("$repo_base_abs") or die "chdir $repo_base_abs failed: $!\n";
|
|
||||||
for my $actual_repo (`find . -type d -name "*.git"|sort`) {
|
|
||||||
chomp ($actual_repo);
|
|
||||||
$actual_repo =~ s/^\.\///;
|
|
||||||
$actual_repo =~ s/\.git$//;
|
|
||||||
# actual_repo has to match the pattern being expanded
|
|
||||||
next unless $actual_repo =~ /$repo/;
|
|
||||||
# if actual_repo is present "as is" in the config, those
|
|
||||||
# permissions will override anything inherited from a
|
|
||||||
# wildcard that also happens to match
|
|
||||||
my $creater;
|
|
||||||
if ($normal_repos{$actual_repo}) {
|
|
||||||
%repos = %normal_repos;
|
|
||||||
$creater = '<gitolite>';
|
|
||||||
} else {
|
|
||||||
# find the creater and subsitute in repos
|
|
||||||
my ($read, $write);
|
|
||||||
($creater, $read, $write) = &repo_rights($repo_base_abs, $actual_repo, $user);
|
|
||||||
# get access list with this
|
|
||||||
&parse_acl($GL_CONF_COMPILED, $actual_repo, $creater, $read || "NOBODY", $write || "NOBODY");
|
|
||||||
$creater = "($creater)";
|
|
||||||
}
|
|
||||||
my $perm = ' ';
|
|
||||||
$perm .= ( $repos{$actual_repo}{R}{'@all'} ? ' @' : ( $repos{$actual_repo}{R}{$user} ? ' R' : ' ' ) );
|
|
||||||
$perm .= ( $repos{$actual_repo}{W}{'@all'} ? ' @' : ( $repos{$actual_repo}{W}{$user} ? ' W' : ' ' ) );
|
|
||||||
next if $perm eq ' ';
|
|
||||||
print "$perm\t$creater\t$actual_repo\n";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# ----------------------------------------------------------------------------
|
|
||||||
# S P E C I A L C O M M A N D S
|
|
||||||
# ----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
sub special_cmd
|
|
||||||
{
|
|
||||||
my ($GL_ADMINDIR, $GL_CONF_COMPILED, $shell_allowed, $RSYNC_BASE, $HTPASSWD_FILE) = @_;
|
|
||||||
|
|
||||||
my $cmd = $ENV{SSH_ORIGINAL_COMMAND};
|
|
||||||
my $user = $ENV{GL_USER};
|
|
||||||
|
|
||||||
# check each special command we know about and call it if enabled
|
|
||||||
if ($cmd eq 'info') {
|
|
||||||
&report_basic($GL_ADMINDIR, $GL_CONF_COMPILED, $user);
|
|
||||||
print "you also have shell access\r\n" if $shell_allowed;
|
|
||||||
} elsif ($cmd =~ /^info\s+(.+)$/) {
|
|
||||||
my @otherusers = split ' ', $1;
|
|
||||||
&parse_acl($GL_CONF_COMPILED);
|
|
||||||
die "you can't ask for others' permissions\n" unless $repos{'gitolite-admin'}{'R'}{$user};
|
|
||||||
for my $otheruser (@otherusers) {
|
|
||||||
warn("ignoring illegal username $otheruser\n"), next unless $otheruser =~ $USERNAME_PATT;
|
|
||||||
&report_basic($GL_ADMINDIR, $GL_CONF_COMPILED, $otheruser);
|
|
||||||
}
|
|
||||||
} elsif ($HTPASSWD_FILE and $cmd eq 'htpasswd') {
|
|
||||||
&ext_cmd_htpasswd($HTPASSWD_FILE);
|
|
||||||
} elsif ($RSYNC_BASE and $cmd =~ /^rsync /) {
|
|
||||||
&ext_cmd_rsync($GL_CONF_COMPILED, $RSYNC_BASE, $cmd);
|
|
||||||
} else {
|
|
||||||
# if the user is allowed a shell, just run the command
|
|
||||||
exec $ENV{SHELL}, "-c", $cmd if $shell_allowed;
|
|
||||||
|
|
||||||
die "bad command: $cmd\n";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# ----------------------------------------------------------------------------
|
|
||||||
# generic check access routine
|
|
||||||
# ----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
sub check_access
|
|
||||||
{
|
|
||||||
my ($GL_CONF_COMPILED, $repo, $path, $perm) = @_;
|
|
||||||
my $ref = "NAME/$path";
|
|
||||||
|
|
||||||
&parse_acl($GL_CONF_COMPILED);
|
|
||||||
|
|
||||||
# until I do some major refactoring (which will bloat the update hook a
|
|
||||||
# bit, sadly), this code duplicates stuff in the current update hook.
|
|
||||||
|
|
||||||
my @allowed_refs;
|
|
||||||
# we want specific perms to override @all, so they come first
|
|
||||||
push @allowed_refs, @ { $repos{$repo}{$ENV{GL_USER}} || [] };
|
|
||||||
push @allowed_refs, @ { $repos{$repo}{'@all'} || [] };
|
|
||||||
|
|
||||||
for my $ar (@allowed_refs) {
|
|
||||||
my $refex = (keys %$ar)[0];
|
|
||||||
next unless $ref =~ /^$refex/;
|
|
||||||
die "$perm $ref $ENV{GL_USER} DENIED by $refex\n" if $ar->{$refex} eq '-';
|
|
||||||
return if ($ar->{$refex} =~ /\Q$perm/);
|
|
||||||
}
|
|
||||||
die "$perm $ref $ENV{GL_REPO} $ENV{GL_USER} DENIED by fallthru\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
# ----------------------------------------------------------------------------
|
|
||||||
# external command helper: rsync
|
|
||||||
# ----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
sub ext_cmd_rsync
|
|
||||||
{
|
|
||||||
my ($GL_CONF_COMPILED, $RSYNC_BASE, $cmd) = @_;
|
|
||||||
|
|
||||||
# test the command patterns; reject if they don't fit. Rsync sends
|
|
||||||
# commands that looks like one of these to the server (the first one is
|
|
||||||
# for a read, the second for a write)
|
|
||||||
# rsync --server --sender -some.flags . some/path
|
|
||||||
# rsync --server -some.flags . some/path
|
|
||||||
|
|
||||||
die "bad rsync command: $cmd"
|
|
||||||
unless $cmd =~ /^rsync --server( --sender)? -[\w.]+(?: --(?:delete|partial))* \. (\S+)$/;
|
|
||||||
my $perm = "W";
|
|
||||||
$perm = "R" if $1;
|
|
||||||
my $path = $2;
|
|
||||||
die "I dont like some of the characters in $path\n" unless $path =~ $REPOPATT_PATT;
|
|
||||||
# XXX make a better pattern for this if people complain ;-)
|
|
||||||
die "I dont like absolute paths in $cmd\n" if $path =~ /^\//;
|
|
||||||
die "I dont like '..' paths in $cmd\n" if $path =~ /\.\./;
|
|
||||||
|
|
||||||
# ok now check if we're permitted to execute a $perm action on $path
|
|
||||||
# (taken as a refex) using rsync.
|
|
||||||
|
|
||||||
&check_access($GL_CONF_COMPILED, 'EXTCMD/rsync', $path, $perm);
|
|
||||||
# that should "die" if there's a problem
|
|
||||||
|
|
||||||
wrap_chdir($RSYNC_BASE);
|
|
||||||
&log_it("$ENV{GL_TS}\t$ENV{SSH_ORIGINAL_COMMAND}\t$ENV{USER}\n");
|
|
||||||
exec $ENV{SHELL}, "-c", $ENV{SSH_ORIGINAL_COMMAND};
|
|
||||||
}
|
|
||||||
|
|
||||||
# ----------------------------------------------------------------------------
|
|
||||||
# external command helper: htpasswd
|
|
||||||
# ----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
sub ext_cmd_htpasswd
|
|
||||||
{
|
|
||||||
my $HTPASSWD_FILE = shift;
|
|
||||||
|
|
||||||
die "$HTPASSWD_FILE doesn't exist or is not writable\n" unless -w $HTPASSWD_FILE;
|
|
||||||
$|++;
|
|
||||||
print <<EOFhtp;
|
|
||||||
Please type in your new htpasswd at the prompt. You only have to type it once.
|
|
||||||
|
|
||||||
NOTE THAT THE PASSWORD WILL BE ECHOED, so please make sure no one is
|
|
||||||
shoulder-surfing, and make sure you clear your screen as well as scrollback
|
|
||||||
history after you're done (or close your terminal instance).
|
|
||||||
|
|
||||||
EOFhtp
|
|
||||||
print "new htpasswd:";
|
|
||||||
|
|
||||||
my $password = <>;
|
|
||||||
$password =~ s/[\n\r]*$//;
|
|
||||||
die "empty passwords are not allowed\n" unless $password;
|
|
||||||
my $rc = system("htpasswd", "-b", $HTPASSWD_FILE, $ENV{GL_USER}, $password);
|
|
||||||
die "htpasswd command seems to have failed with $rc return code...\n" if $rc;
|
|
||||||
}
|
|
||||||
|
|
||||||
1;
|
|
|
@ -1,200 +0,0 @@
|
||||||
#!/usr/bin/perl
|
|
||||||
|
|
||||||
use strict;
|
|
||||||
use warnings;
|
|
||||||
|
|
||||||
# === auth-command ===
|
|
||||||
# the command that GL users actually run
|
|
||||||
|
|
||||||
# part of the gitolite (GL) suite
|
|
||||||
|
|
||||||
# how run: via sshd, being listed in "command=" in ssh authkeys
|
|
||||||
# when: every login by a GL user
|
|
||||||
# input: $1 is GL username, plus $SSH_ORIGINAL_COMMAND
|
|
||||||
# output:
|
|
||||||
# security:
|
|
||||||
# - currently, we just make some basic checks, copied from gitosis
|
|
||||||
|
|
||||||
# robustness:
|
|
||||||
|
|
||||||
# other notes:
|
|
||||||
|
|
||||||
# ----------------------------------------------------------------------------
|
|
||||||
# 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);
|
|
||||||
# and these are set by gitolite.pm
|
|
||||||
our ($R_COMMANDS, $W_COMMANDS, $REPONAME_PATT, $REPOPATT_PATT);
|
|
||||||
our %repos;
|
|
||||||
|
|
||||||
# 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 the umask before creating any files
|
|
||||||
umask($REPO_UMASK);
|
|
||||||
|
|
||||||
my $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[0] eq '-s') {
|
|
||||||
$shell_allowed = 1;
|
|
||||||
shift;
|
|
||||||
}
|
|
||||||
|
|
||||||
# first, fix the biggest gripe I have with gitosis, a 1-line change
|
|
||||||
my $user=$ENV{GL_USER}=shift; # there; now that's available everywhere!
|
|
||||||
|
|
||||||
# ----------------------------------------------------------------------------
|
|
||||||
# logging, timestamp env vars
|
|
||||||
# ----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
# timestamp
|
|
||||||
my ($s, $min, $h, $d, $m, $y) = (localtime)[0..5];
|
|
||||||
$y += 1900; $m++; # usual adjustments
|
|
||||||
for ($s, $min, $h, $d, $m) {
|
|
||||||
$_ = "0$_" if $_ < 10;
|
|
||||||
}
|
|
||||||
$ENV{GL_TS} = "$y-$m-$d.$h:$min:$s";
|
|
||||||
|
|
||||||
# substitute template parameters and set the logfile name
|
|
||||||
$GL_LOGT =~ s/%y/$y/g;
|
|
||||||
$GL_LOGT =~ s/%m/$m/g;
|
|
||||||
$GL_LOGT =~ s/%d/$d/g;
|
|
||||||
$ENV{GL_LOG} = $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"
|
|
||||||
exec { $ENV{SHELL} } $shell;
|
|
||||||
}
|
|
||||||
# otherwise, pretend he typed in "info" and carry on...
|
|
||||||
$ENV{SSH_ORIGINAL_COMMAND} = 'info';
|
|
||||||
}
|
|
||||||
|
|
||||||
# ----------------------------------------------------------------------------
|
|
||||||
# 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_base_abs, $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_base_abs, $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_CONF_COMPILED, $repo_base_abs, $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"), just like gitosis does, although I'm not sure how necessary that is.
|
|
||||||
# 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 =~ $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);
|
|
||||||
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
|
|
||||||
# ----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
if ( -d "$repo_base_abs/$repo.git" ) {
|
|
||||||
# existing repo
|
|
||||||
my ($creater, $user_R, $user_W) = &repo_rights($repo_base_abs, $repo, $user);
|
|
||||||
&parse_acl($GL_CONF_COMPILED, $repo, $creater, $user_R, $user_W);
|
|
||||||
} else {
|
|
||||||
&parse_acl($GL_CONF_COMPILED, $repo, $user, $user, $user);
|
|
||||||
|
|
||||||
# auto-vivify new repo if you have C access (and wildrepos is on)
|
|
||||||
if ( $GL_WILDREPOS and $repos{$repo}{C}{$user} || $repos{$repo}{C}{'@all'} ) {
|
|
||||||
wrap_chdir("$repo_base_abs");
|
|
||||||
new_repo($repo, "$GL_ADMINDIR/hooks/common", $user);
|
|
||||||
wrap_chdir($ENV{HOME});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# we know the user and repo; we just need to know what perm he's trying
|
|
||||||
my $perm = ($verb =~ $R_COMMANDS ? 'R' : 'W');
|
|
||||||
|
|
||||||
die "$perm access for $repo DENIED to $user\n"
|
|
||||||
unless $repos{$repo}{$perm}{$user}
|
|
||||||
or $repos{$repo}{$perm}{'@all'};
|
|
||||||
|
|
||||||
# ----------------------------------------------------------------------------
|
|
||||||
# over to git now
|
|
||||||
# ----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
&log_it("$ENV{GL_TS}\t$ENV{SSH_ORIGINAL_COMMAND}\t$user\n");
|
|
||||||
|
|
||||||
$repo = "'$REPO_BASE/$repo.git'";
|
|
||||||
exec("git", "shell", "-c", "$verb $repo");
|
|
|
@ -1,560 +0,0 @@
|
||||||
#!/usr/bin/perl
|
|
||||||
|
|
||||||
use strict;
|
|
||||||
use warnings;
|
|
||||||
use Data::Dumper;
|
|
||||||
$Data::Dumper::Indent = 1;
|
|
||||||
$Data::Dumper::Sortkeys = 1;
|
|
||||||
$Data::Dumper::Sortkeys = sub { return [ reverse sort keys %{$_[0]} ]; };
|
|
||||||
# this is to make sure that $creater etc go to the end of the dumped hash.
|
|
||||||
# Without this, a setup that has something like
|
|
||||||
# @team = u1 u2 u3
|
|
||||||
# repo priv/CREATER/.+
|
|
||||||
# RW+ = CREATER
|
|
||||||
# RW = @team
|
|
||||||
# has a problem. The RW overrides the RW+ when the dumped hash is read in
|
|
||||||
# (simply going by sequence), so creater's special privs are lost
|
|
||||||
|
|
||||||
# === 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, $SHELL_USERS, $GL_WILDREPOS, $GL_GITCONFIG_KEYS, $GL_PACKAGE_HOOKS);
|
|
||||||
# and these are set by gitolite.pm
|
|
||||||
our ($REPONAME_PATT, $REPOPATT_PATT, $USERNAME_PATT, $AUTH_COMMAND, $AUTH_OPTIONS, $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
|
|
||||||
# ----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
# command and options for authorized_keys
|
|
||||||
$AUTH_COMMAND="$bindir/gl-auth-command";
|
|
||||||
$AUTH_OPTIONS="no-port-forwarding,no-X11-forwarding,no-agent-forwarding";
|
|
||||||
# note, for most users there's also a "no-pty" added to this, see later
|
|
||||||
|
|
||||||
# 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 = ();
|
|
||||||
|
|
||||||
# <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 = ();
|
|
||||||
|
|
||||||
# catch usernames<->pubkeys mismatches; search for "lint" below
|
|
||||||
my %user_list = ();
|
|
||||||
|
|
||||||
# repo configurations
|
|
||||||
my %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 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;
|
|
||||||
while (<$conf_fh>)
|
|
||||||
{
|
|
||||||
# kill comments, but take care of "#" inside *simple* strings
|
|
||||||
s/^((".*?"|[^#"])*)#.*/$1/;
|
|
||||||
# normalise whitespace; keeps later regexes very simple
|
|
||||||
s/=/ = /;
|
|
||||||
s/\s+/ /g;
|
|
||||||
s/^ //;
|
|
||||||
s/ $//;
|
|
||||||
# and blank lines
|
|
||||||
next unless /\S/;
|
|
||||||
|
|
||||||
# user or repo groups
|
|
||||||
if (/^(@\S+) = (.*)/)
|
|
||||||
{
|
|
||||||
# store the members of each group as hash key. Keep track of when
|
|
||||||
# the group was *first* created by using $fragment as the *value*
|
|
||||||
do { $groups{$1}{$_} ||= $fragment } for ( expand_list( split(' ', $2) ) );
|
|
||||||
die "$ABRT bad group $1\n" unless $1 =~ $REPONAME_PATT;
|
|
||||||
}
|
|
||||||
# repo(s)
|
|
||||||
elsif (/^repo (.*)/)
|
|
||||||
{
|
|
||||||
# grab the list and expand any @stuff in it
|
|
||||||
@repos = split ' ', $1;
|
|
||||||
if (@repos == 1 and $repos[0] eq '@all') {
|
|
||||||
@repos = keys %repos;
|
|
||||||
} else {
|
|
||||||
@repos = expand_list ( @repos );
|
|
||||||
do { die "$ABRT bad reponame $_\n" unless ($GL_WILDREPOS ? $_ =~ $REPOPATT_PATT : $_ =~ $REPONAME_PATT) } for @repos;
|
|
||||||
}
|
|
||||||
s/\bCREAT[EO]R\b/\$creater/g for @repos;
|
|
||||||
}
|
|
||||||
# actual permission line
|
|
||||||
elsif (/^(-|C|R|RW|RW\+) (.* )?= (.+)/)
|
|
||||||
{
|
|
||||||
my $perms = $1;
|
|
||||||
my @refs; @refs = split(' ', $2) if $2;
|
|
||||||
@refs = expand_list ( @refs );
|
|
||||||
my @users = split ' ', $3;
|
|
||||||
die "wildrepos disabled, 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;
|
|
||||||
|
|
||||||
# expand the user list, unless it is just "@all"
|
|
||||||
@users = expand_list ( @users )
|
|
||||||
unless (@users == 1 and $users[0] eq '@all');
|
|
||||||
do { die "$ABRT bad username $_\n" unless $_ =~ $USERNAME_PATT } for @users;
|
|
||||||
|
|
||||||
s/\bCREAT[EO]R\b/\$creater/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) # 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 (
|
|
||||||
# processing the master config, not a fragment
|
|
||||||
( $fragment eq 'master' ) or
|
|
||||||
# fragment is also called 'foo' (you're allowed to have a
|
|
||||||
# fragment that is only concerned with one repo)
|
|
||||||
( $fragment eq $repo ) or
|
|
||||||
# fragment is called "bar" and "@bar = foo" has been
|
|
||||||
# defined in the master config
|
|
||||||
( ($groups{"\@$fragment"}{$repo} || '') eq 'master' )
|
|
||||||
) {
|
|
||||||
# all these are fine
|
|
||||||
} else {
|
|
||||||
# this is a little more complex
|
|
||||||
|
|
||||||
# fragment is called "bar", one or more "@bar = regex"
|
|
||||||
# have been specified in master, and "foo" matches some
|
|
||||||
# such "regex"
|
|
||||||
my @matched = grep { $repo =~ /^$_$/ }
|
|
||||||
grep { $groups{"\@$fragment"}{$_} eq 'master' }
|
|
||||||
sort keys %{ $groups{"\@$fragment"} };
|
|
||||||
if (@matched < 1) {
|
|
||||||
$ignored{$fragment}{$repo} = 1;
|
|
||||||
next;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for my $user (@users)
|
|
||||||
{
|
|
||||||
$user_list{$user}++; # only to catch lint, see later
|
|
||||||
|
|
||||||
# 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/;
|
|
||||||
|
|
||||||
# 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\//;
|
|
||||||
push @{ $repos{$repo}{$user} }, { $ref => $perms }
|
|
||||||
unless $rurp_seen{$repo}{$user}{$ref}{$perms}++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
# configuration
|
|
||||||
elsif (/^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) # each repo in the current stanza
|
|
||||||
{
|
|
||||||
$repo_config{$repo}{$key} = $value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
# include
|
|
||||||
elsif (/^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 (/^(\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
|
|
||||||
$fragment ne 'master' and $fragment ne $repo and ($groups{"\@$fragment"}{$repo} || '') ne 'master';
|
|
||||||
$desc{"$repo.git"} = $desc;
|
|
||||||
$owner{"$repo.git"} = $owner || '';
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
die "$ABRT can't make head or tail of '$_'\n";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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 );
|
|
||||||
my $dumped_data = Data::Dumper->Dump([\%repos], [qw(*repos)]);
|
|
||||||
# the dump uses single quotes, but we convert any strings containing $creater,
|
|
||||||
# $readers, $writers, to double quoted strings. A wee bit sneaky, but not too
|
|
||||||
# much...
|
|
||||||
$dumped_data =~ s/'(?=[^']*\$(?:creater|readers|writers))(.*?)'/"$1"/g;
|
|
||||||
print $compiled_fh $dumped_data;
|
|
||||||
close $compiled_fh or die "$ABRT close compiled-conf failed: $!\n";
|
|
||||||
|
|
||||||
# ----------------------------------------------------------------------------
|
|
||||||
# any new repos to be created?
|
|
||||||
# ----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
# modern gits allow cloning from an empty repo, so we just create it. Gitosis
|
|
||||||
# did not have that luxury, so it was forced to detect the first push and
|
|
||||||
# create it then
|
|
||||||
|
|
||||||
# but it turns out not everyone has "modern" gits :)
|
|
||||||
my $git_version = `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"
|
|
||||||
|
|
||||||
# repo-base needs to be an absolute path for this loop to work right
|
|
||||||
# so if it was not already absolute, prefix $HOME.
|
|
||||||
my $repo_base_abs = ( $REPO_BASE =~ m(^/) ? $REPO_BASE : "$ENV{HOME}/$REPO_BASE" );
|
|
||||||
|
|
||||||
wrap_chdir("$repo_base_abs");
|
|
||||||
|
|
||||||
for my $repo (sort keys %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("$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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
warn "\n\t\t***** WARNING *****\n" .
|
|
||||||
"\tyour git version is older than 1.6.2\n" .
|
|
||||||
"\tgitolite will work but you MUST read the section on\n" .
|
|
||||||
"\t\"git version dependency\" in doc/3-faq-tips-etc.mkd\n"
|
|
||||||
if $git_version < 10602; # that's 1.6.2 to you
|
|
||||||
|
|
||||||
# ----------------------------------------------------------------------------
|
|
||||||
# update repo configurations
|
|
||||||
# ----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
for my $repo (keys %repo_config) {
|
|
||||||
wrap_chdir("$repo_base_abs/$repo.git");
|
|
||||||
while ( my ($key, $value) = each(%{ $repo_config{$repo} }) ) {
|
|
||||||
if ($value) {
|
|
||||||
$value =~ s/^"(.*)"$/$1/;
|
|
||||||
system("git", "config", $key, $value);
|
|
||||||
} else {
|
|
||||||
system("git", "config", "--unset-all", $key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# ----------------------------------------------------------------------------
|
|
||||||
# handle gitweb and daemon
|
|
||||||
# ----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
# How you specify gitweb and daemon access is quite different from gitosis. I
|
|
||||||
# just assume you'll never have any *real* users called "gitweb" or "daemon"
|
|
||||||
# :-) These are now "pseduo users" -- giving them "R" access to a repo is all
|
|
||||||
# you have to do
|
|
||||||
|
|
||||||
wrap_chdir("$repo_base_abs");
|
|
||||||
|
|
||||||
# daemons first...
|
|
||||||
for my $repo (sort keys %repos) {
|
|
||||||
next unless $repo =~ $REPONAME_PATT;
|
|
||||||
my $export_ok = "$repo.git/git-daemon-export-ok";
|
|
||||||
if ($repos{$repo}{'R'}{'daemon'}) {
|
|
||||||
system("touch $export_ok");
|
|
||||||
} else {
|
|
||||||
unlink($export_ok);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
my %projlist = ();
|
|
||||||
# ...then gitwebs
|
|
||||||
for my $repo (sort keys %repos) {
|
|
||||||
next unless $repo =~ $REPONAME_PATT;
|
|
||||||
my $desc_file = "$repo.git/description";
|
|
||||||
# note: having a description also counts as enabling gitweb
|
|
||||||
if ($repos{$repo}{'R'}{'gitweb'} or $desc{"$repo.git"}) {
|
|
||||||
$projlist{"$repo.git"} = 1;
|
|
||||||
# add the description file; no messages to user or error checking :)
|
|
||||||
$desc{"$repo.git"} and open(DESC, ">", $desc_file) and print DESC $desc{"$repo.git"} . "\n" and close DESC;
|
|
||||||
if ($owner{"$repo.git"}) {
|
|
||||||
# set the repository owner
|
|
||||||
system("git", "--git-dir=$repo.git", "config", "gitweb.owner", $owner{"$repo.git"});
|
|
||||||
} else {
|
|
||||||
# remove the repository owner setting
|
|
||||||
system("git --git-dir=$repo.git config --unset-all gitweb.owner 2>/dev/null");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
# delete the description file; no messages to user or error checking :)
|
|
||||||
unlink $desc_file;
|
|
||||||
# remove the repository owner setting
|
|
||||||
system("git --git-dir=$repo.git config --unset-all gitweb.owner 2>/dev/null");
|
|
||||||
}
|
|
||||||
|
|
||||||
# unless there are other gitweb.* keys set, remove the section to keep the
|
|
||||||
# config file clean
|
|
||||||
my $keys = `git --git-dir=$repo.git config --get-regexp '^gitweb\\.' 2>/dev/null`;
|
|
||||||
if (length($keys) == 0) {
|
|
||||||
system("git --git-dir=$repo.git config --remove-section gitweb 2>/dev/null");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# update 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
|
|
||||||
# ----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
my $authkeys_fh = wrap_open( "<", $ENV{HOME} . "/.ssh/authorized_keys",
|
|
||||||
"\tFor security reasons, gitolite will not *create* this file if it does\n" .
|
|
||||||
"\tnot already exist. Please see the \"admin\" document for details\n");
|
|
||||||
my $newkeys_fh = wrap_open( ">", $ENV{HOME} . "/.ssh/new_authkeys" );
|
|
||||||
# save existing authkeys minus the GL-added stuff
|
|
||||||
while (<$authkeys_fh>)
|
|
||||||
{
|
|
||||||
print $newkeys_fh $_ unless (/^# gito(sis-)?lite start/../^# gito(sis-)?lite end/);
|
|
||||||
}
|
|
||||||
|
|
||||||
# add our "start" line, each key on its own line (prefixed by command and
|
|
||||||
# options, in the standard ssh authorized_keys format), then the "end" line.
|
|
||||||
print $newkeys_fh "# gitolite start\n";
|
|
||||||
wrap_chdir($GL_KEYDIR);
|
|
||||||
for my $pubkey (glob("*"))
|
|
||||||
{
|
|
||||||
# lint check 1
|
|
||||||
unless ($pubkey =~ /\.pub$/)
|
|
||||||
{
|
|
||||||
print STDERR "WARNING: pubkey files should end with \".pub\", ignoring $pubkey\n";
|
|
||||||
next;
|
|
||||||
}
|
|
||||||
my $user = $pubkey; $user =~ s/(\@[^.]+)?\.pub$//;
|
|
||||||
# lint check 2
|
|
||||||
print STDERR "WARNING: pubkey $pubkey exists but user $user not in config\n"
|
|
||||||
unless $user_list{$user};
|
|
||||||
$user_list{$user} = 'has pubkey';
|
|
||||||
# apparently some pubkeys don't end in a newline...
|
|
||||||
my $pubkey_content = `cat $pubkey`;
|
|
||||||
$pubkey_content =~ s/\s*$/\n/;
|
|
||||||
# don't trust files with multiple lines (i.e., something after a newline)
|
|
||||||
if ($pubkey_content =~ /\n./)
|
|
||||||
{
|
|
||||||
print STDERR "WARNING: a pubkey file can only have one line (key); ignoring $pubkey\n";
|
|
||||||
next;
|
|
||||||
}
|
|
||||||
if ($SHELL_USERS and $SHELL_USERS =~ /(^|\s)$user(\s|$)/) {
|
|
||||||
print $newkeys_fh "command=\"$AUTH_COMMAND -s $user\",$AUTH_OPTIONS ";
|
|
||||||
} else {
|
|
||||||
print $newkeys_fh "command=\"$AUTH_COMMAND $user\",$AUTH_OPTIONS,no-pty ";
|
|
||||||
}
|
|
||||||
print $newkeys_fh $pubkey_content;
|
|
||||||
}
|
|
||||||
# lint check 3; a little more severe than the first two I guess...
|
|
||||||
for my $user (sort keys %user_list)
|
|
||||||
{
|
|
||||||
next if $user =~ /^(gitweb|daemon|\@all|\$creater|\$readers|\$writers)$/ or $user_list{$user} eq 'has pubkey';
|
|
||||||
print STDERR "$WARN user $user in config, but has no pubkey!\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
print $newkeys_fh "# gitolite end\n";
|
|
||||||
close $newkeys_fh or die "$ABRT close newkeys failed: $!\n";
|
|
||||||
|
|
||||||
# all done; overwrite the file (use cat to avoid perm changes)
|
|
||||||
system("cat $ENV{HOME}/.ssh/authorized_keys > $ENV{HOME}/.ssh/old_authkeys");
|
|
||||||
system("cat $ENV{HOME}/.ssh/new_authkeys > $ENV{HOME}/.ssh/authorized_keys")
|
|
||||||
and die "couldn't write authkeys file\n";
|
|
||||||
system("rm $ENV{HOME}/.ssh/new_authkeys");
|
|
|
@ -1,101 +0,0 @@
|
||||||
#!/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();
|
|
|
@ -1,620 +0,0 @@
|
||||||
#!/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.
|
|
||||||
|
|
||||||
Example usage: $0 git my.git.server sitaram
|
|
||||||
|
|
||||||
Notes:
|
|
||||||
- "user" and "admin_name" 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
|
|
||||||
# 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
|
|
||||||
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"
|
|
||||||
|
|
||||||
# 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
|
|
||||||
|
|
||||||
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
|
|
||||||
git describe --tags --long HEAD 2>/dev/null > conf/VERSION || echo '(unknown)' > conf/VERSION
|
|
||||||
|
|
||||||
# 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 gitolite
|
|
||||||
user $user
|
|
||||||
hostname $host
|
|
||||||
port $port
|
|
||||||
identityfile ~/.ssh/$admin_name" > $tmpgli/.gl-stanza
|
|
||||||
|
|
||||||
if grep 'host *gitolite' "$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 -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 $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 $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 $tmpgli/gitolite.conf $user@$host:$GL_ADMINDIR/conf/
|
|
||||||
scp $quiet -P $port "$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 gitolite-admin ]]
|
|
||||||
then
|
|
||||||
echo $HOME/gitolite-admin exists, skipping clone step...
|
|
||||||
else
|
|
||||||
prompt "cloning gitolite-admin repo..." "$v_cloning"
|
|
||||||
git clone gitolite:gitolite-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!
|
|
||||||
|
|
||||||
Reminder:
|
|
||||||
*Your* URL for cloning any repo on this server will be
|
|
||||||
gitolite:reponame.git
|
|
||||||
|
|
||||||
*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.
|
|
||||||
"
|
|
|
@ -1,40 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# what/why: re-establish gitolite admin access when admin key(s) lost
|
|
||||||
# where: on server (NOT client!)
|
|
||||||
|
|
||||||
# pre-req: shell access to the server (even with password is fine)
|
|
||||||
# pre-work: - make yourself a new keypair on your workstation
|
|
||||||
# - copy the pubkey and this script to the server
|
|
||||||
|
|
||||||
# usage: $0 admin_name client_host_shortname pubkeyfile
|
|
||||||
# notes: - admin_name should already have RW or RW+ access to the
|
|
||||||
# gitolite-admin repo
|
|
||||||
# - client_host_shortname is any simple word; see example below
|
|
||||||
|
|
||||||
# WARNING: ABSOLUTELY NO ARGUMENT CHECKING DONE
|
|
||||||
# WARNING: NEWER GITS ONLY ON SERVER SIDE (for now)
|
|
||||||
|
|
||||||
# example: $0 sitaram laptop /tmp/sitaram.pub
|
|
||||||
# result: a new keyfile named sitaram@laptop.pub would be added
|
|
||||||
|
|
||||||
# ENDHELP
|
|
||||||
|
|
||||||
[[ -z $1 ]] && { perl -pe "s(\\\$0)($0); last if /ENDHELP/" < $0; exit 1; }
|
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
cd
|
|
||||||
REPO_BASE=$( perl -e 'do ".gitolite.rc"; print $REPO_BASE' )
|
|
||||||
GL_ADMINDIR=$(perl -e 'do ".gitolite.rc"; print $GL_ADMINDIR')
|
|
||||||
|
|
||||||
cd; cd $GL_ADMINDIR/keydir; pwd
|
|
||||||
cp -v $3 $1@$2.pub
|
|
||||||
|
|
||||||
cd; cd $REPO_BASE/gitolite-admin.git; pwd
|
|
||||||
# XXX FIXME TODO -- fix this to work with older gits also
|
|
||||||
GIT_WORK_TREE=$GL_ADMINDIR git add keydir
|
|
||||||
GIT_WORK_TREE=$GL_ADMINDIR git commit -m "emergency add $1@$2.pub"
|
|
||||||
|
|
||||||
cd $GL_ADMINDIR
|
|
||||||
src/gl-compile-conf
|
|
107
src/gl-install
107
src/gl-install
|
@ -1,107 +0,0 @@
|
||||||
#!/usr/bin/perl
|
|
||||||
|
|
||||||
use strict;
|
|
||||||
use warnings;
|
|
||||||
|
|
||||||
our ($REPO_BASE, $GL_ADMINDIR, $GL_CONF, $GIT_PATH, $GL_PACKAGE_CONF, $GL_PACKAGE_HOOKS);
|
|
||||||
|
|
||||||
# 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;
|
|
||||||
if ( -d $dir ) {
|
|
||||||
print "$dir already exists\n";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
mkdir($dir) or die "mkdir $dir failed: $!\n";
|
|
||||||
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;
|
|
||||||
|
|
||||||
# mkdir $REPO_BASE, $GL_ADMINDIR if they don't already exist
|
|
||||||
my $repo_base_abs = ( $REPO_BASE =~ m(^/) ? $REPO_BASE : "$ENV{HOME}/$REPO_BASE" );
|
|
||||||
wrap_mkdir($repo_base_abs);
|
|
||||||
wrap_mkdir($GL_ADMINDIR);
|
|
||||||
# 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");
|
|
||||||
}
|
|
||||||
|
|
||||||
# "src" and "doc" will be overwritten on each install, but not conf
|
|
||||||
if ($GL_PACKAGE_HOOKS) {
|
|
||||||
system("cp -R $GL_PACKAGE_HOOKS $GL_ADMINDIR");
|
|
||||||
} else {
|
|
||||||
system("cp -R $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("$repo_base_abs") or die "chdir $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 install.pl update-hook.pl hooks/update ga-post-update-hook VERSION) {
|
|
||||||
unlink "$GL_ADMINDIR/src/$oldname";
|
|
||||||
unlink "$ENV{HOME}/gitolite-install/src/$oldname";
|
|
||||||
}
|
|
92
src/gl-setup
92
src/gl-setup
|
@ -1,92 +0,0 @@
|
||||||
#!/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 "$@"; echo death at line number ${BASH_LINENO[0]}; exit 1; }
|
|
||||||
|
|
||||||
pubkey_file=$1
|
|
||||||
admin_name=
|
|
||||||
if [[ -n $pubkey_file ]]
|
|
||||||
then
|
|
||||||
[[ $pubkey_file =~ .pub$ ]] || die "$pubkey_file must end in .pub"
|
|
||||||
[[ -f $pubkey_file ]] || die "cant find $pubkey_file"
|
|
||||||
admin_name=$(basename $pubkey_file .pub)
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ -f ~/.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 /^\$/;' < ~/.gitolite.rc | sort > .oldvars
|
|
||||||
comm -23 .newvars .oldvars > .diffvars
|
|
||||||
if [[ -s .diffvars ]]
|
|
||||||
then
|
|
||||||
cp $GL_PACKAGE_CONF/example.gitolite.rc ~/.gitolite.rc.new
|
|
||||||
echo new version of the rc file saved in ~/.gitolite.rc.new
|
|
||||||
echo
|
|
||||||
echo please update ~/.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 $pubkey_file ]] || die "looks like first run -- I need a pubkey file"
|
|
||||||
cp $GL_PACKAGE_CONF/example.gitolite.rc ~/.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
|
|
||||||
mkdir -p .ssh
|
|
||||||
touch .ssh/authorized_keys
|
|
||||||
chmod go-w . .ssh .ssh/authorized_keys
|
|
||||||
|
|
||||||
# now we get to gitolite itself
|
|
||||||
|
|
||||||
gl-install -q
|
|
||||||
|
|
||||||
GL_ADMINDIR=$(cd;perl -e 'do ".gitolite.rc"; print $GL_ADMINDIR')
|
|
||||||
REPO_BASE=$( cd;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 || 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
|
|
324
src/lib/Gitolite/Common.pm
Normal file
324
src/lib/Gitolite/Common.pm
Normal file
|
@ -0,0 +1,324 @@
|
||||||
|
package Gitolite::Common;
|
||||||
|
|
||||||
|
# common (non-gitolite-specific) functions
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
#<<<
|
||||||
|
@EXPORT = qw(
|
||||||
|
print2 dbg _mkdir _open ln_sf tsh_rc sort_u
|
||||||
|
say _warn _chdir _print tsh_text list_phy_repos
|
||||||
|
say2 _die _system slurp tsh_lines
|
||||||
|
trace cleanup_conf_line tsh_try
|
||||||
|
usage tsh_run
|
||||||
|
gen_lfn
|
||||||
|
gl_log
|
||||||
|
|
||||||
|
dd
|
||||||
|
t_start
|
||||||
|
t_lap
|
||||||
|
);
|
||||||
|
#>>>
|
||||||
|
use Exporter 'import';
|
||||||
|
use File::Path qw(mkpath);
|
||||||
|
use Carp qw(carp cluck croak confess);
|
||||||
|
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
sub print2 {
|
||||||
|
local $/ = "\n";
|
||||||
|
print STDERR @_;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub say {
|
||||||
|
local $/ = "\n";
|
||||||
|
print @_, "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
sub say2 {
|
||||||
|
local $/ = "\n";
|
||||||
|
print STDERR @_, "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
sub trace {
|
||||||
|
gl_log( "\t" . join( ",", @_[ 1 .. $#_ ] ) ) if $_[0] <= 1 and defined $Gitolite::Rc::rc{LOG_EXTRA};
|
||||||
|
|
||||||
|
return unless defined( $ENV{D} );
|
||||||
|
|
||||||
|
my $level = shift; return if $ENV{D} < $level;
|
||||||
|
my $args = ''; $args = join( ", ", @_ ) if @_;
|
||||||
|
my $sub = ( caller 1 )[3] || ''; $sub =~ s/.*://;
|
||||||
|
if ( not $sub ) {
|
||||||
|
$sub = (caller)[1];
|
||||||
|
$sub =~ s(.*/(.*))(($1));
|
||||||
|
}
|
||||||
|
$sub .= ' ' x ( 32 - length($sub) );
|
||||||
|
say2 "TRACE $level $sub", ( @_ ? shift : () );
|
||||||
|
say2( "TRACE $level " . ( " " x 32 ), $_ ) for @_;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub dbg {
|
||||||
|
use Data::Dumper;
|
||||||
|
return unless defined( $ENV{D} );
|
||||||
|
for my $i (@_) {
|
||||||
|
print STDERR "DBG: " . Dumper($i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub dd {
|
||||||
|
local $ENV{D} = 1;
|
||||||
|
dbg(@_);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
use Time::HiRes;
|
||||||
|
my %start_times;
|
||||||
|
|
||||||
|
sub t_start {
|
||||||
|
my $name = shift || 'default';
|
||||||
|
$start_times{$name} = [ Time::HiRes::gettimeofday() ];
|
||||||
|
}
|
||||||
|
|
||||||
|
sub t_lap {
|
||||||
|
my $name = shift || 'default';
|
||||||
|
return Time::HiRes::tv_interval( $start_times{$name} );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub _warn {
|
||||||
|
gl_log( 'warn', @_ );
|
||||||
|
if ( $ENV{D} and $ENV{D} >= 3 ) {
|
||||||
|
cluck "WARNING: ", @_, "\n";
|
||||||
|
} elsif ( defined( $ENV{D} ) ) {
|
||||||
|
carp "WARNING: ", @_, "\n";
|
||||||
|
} else {
|
||||||
|
warn "WARNING: ", @_, "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$SIG{__WARN__} = \&_warn;
|
||||||
|
|
||||||
|
sub _die {
|
||||||
|
gl_log( 'die', @_ );
|
||||||
|
if ( $ENV{D} and $ENV{D} >= 3 ) {
|
||||||
|
confess "FATAL: " . join( ",", @_ ) . "\n" if defined( $ENV{D} );
|
||||||
|
} elsif ( defined( $ENV{D} ) ) {
|
||||||
|
croak "FATAL: " . join( ",", @_ ) . "\n";
|
||||||
|
} else {
|
||||||
|
die "FATAL: " . join( ",", @_ ) . "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$SIG{__DIE__} = \&_die;
|
||||||
|
|
||||||
|
sub usage {
|
||||||
|
_warn(shift) if @_;
|
||||||
|
my $script = (caller)[1];
|
||||||
|
my $function = ( ( ( caller(1) )[3] ) || ( ( caller(0) )[3] ) );
|
||||||
|
$function =~ s/.*:://;
|
||||||
|
my $code = slurp($script);
|
||||||
|
$code =~ /^=for $function\b(.*?)^=cut/sm;
|
||||||
|
say2( $1 ? $1 : "...no usage message in $script" );
|
||||||
|
exit 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub _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
|
||||||
|
my $dir = shift;
|
||||||
|
my $perm = shift; # optional
|
||||||
|
return if -d $dir;
|
||||||
|
mkpath($dir);
|
||||||
|
chmod $perm, $dir if $perm;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub _chdir {
|
||||||
|
chdir( $_[0] || $ENV{HOME} ) or _die "chdir $_[0] failed: $!\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
sub _system {
|
||||||
|
# run system(), catch errors. Be verbose only if $ENV{D} exists. If not,
|
||||||
|
# exit with <rc of system()> if it applies, else just "exit 1".
|
||||||
|
trace( 1, 'system', @_ );
|
||||||
|
if ( system(@_) != 0 ) {
|
||||||
|
trace( 1, "system() failed", @_, "-> $?" );
|
||||||
|
if ( $? == -1 ) {
|
||||||
|
die "failed to execute: $!\n" if $ENV{D};
|
||||||
|
} elsif ( $? & 127 ) {
|
||||||
|
die "child died with signal " . ( $? & 127 ) . "\n" if $ENV{D};
|
||||||
|
} else {
|
||||||
|
die "child exited with value " . ( $? >> 8 ) . "\n" if $ENV{D};
|
||||||
|
exit( $? >> 8 );
|
||||||
|
}
|
||||||
|
exit 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub _open {
|
||||||
|
open( my $fh, $_[0], $_[1] ) or _die "open $_[1] failed: $!\n";
|
||||||
|
return $fh;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub _print {
|
||||||
|
my ( $file, @text ) = @_;
|
||||||
|
my $fh = _open( ">", "$file.$$" );
|
||||||
|
print $fh @text;
|
||||||
|
close($fh) or _die "close $file failed: $! at ", (caller)[1], " line ", (caller)[2], "\n";
|
||||||
|
my $oldmode = ( ( stat $file )[2] );
|
||||||
|
rename "$file.$$", $file;
|
||||||
|
chmod $oldmode, $file if $oldmode;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub slurp {
|
||||||
|
return unless defined wantarray;
|
||||||
|
local $/ = undef unless wantarray;
|
||||||
|
my $fh = _open( "<", $_[0] );
|
||||||
|
return <$fh>;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub dos2unix {
|
||||||
|
# WARNING: when calling this, make sure you supply a list context
|
||||||
|
s/\r\n/\n/g for @_;
|
||||||
|
return @_;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub ln_sf {
|
||||||
|
trace( 3, @_ );
|
||||||
|
my ( $srcdir, $glob, $dstdir ) = @_;
|
||||||
|
for my $hook ( glob("$srcdir/$glob") ) {
|
||||||
|
$hook =~ s/$srcdir\///;
|
||||||
|
unlink "$dstdir/$hook";
|
||||||
|
symlink "$srcdir/$hook", "$dstdir/$hook" or croak "could not symlink $srcdir/$hook to $dstdir\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub sort_u {
|
||||||
|
my %uniq;
|
||||||
|
my $listref = shift;
|
||||||
|
return [] unless @{$listref};
|
||||||
|
undef @uniq{ @{$listref} }; # expect a listref
|
||||||
|
my @sort_u = sort keys %uniq;
|
||||||
|
return \@sort_u;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub cleanup_conf_line {
|
||||||
|
my $line = shift;
|
||||||
|
|
||||||
|
# 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
my @phy_repos = ();
|
||||||
|
|
||||||
|
sub list_phy_repos {
|
||||||
|
# use cached value only if it exists *and* no arg was received (i.e.,
|
||||||
|
# receiving *any* arg invalidates cache)
|
||||||
|
return \@phy_repos if ( @phy_repos and not @_ );
|
||||||
|
|
||||||
|
for my $repo (`find . -name "*.git" -prune`) {
|
||||||
|
chomp($repo);
|
||||||
|
$repo =~ s(\./(.*)\.git$)($1);
|
||||||
|
push @phy_repos, $repo;
|
||||||
|
}
|
||||||
|
trace( 2, scalar(@phy_repos) . " physical repos found" );
|
||||||
|
return sort_u( \@phy_repos );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# generate a timestamp
|
||||||
|
sub gen_ts {
|
||||||
|
my ( $s, $min, $h, $d, $m, $y ) = (localtime)[ 0 .. 5 ];
|
||||||
|
$y += 1900; $m++; # usual adjustments
|
||||||
|
for ( $s, $min, $h, $d, $m ) {
|
||||||
|
$_ = "0$_" if $_ < 10;
|
||||||
|
}
|
||||||
|
my $ts = "$y-$m-$d.$h:$min:$s";
|
||||||
|
|
||||||
|
return $ts;
|
||||||
|
}
|
||||||
|
|
||||||
|
# generate a log file name
|
||||||
|
sub gen_lfn {
|
||||||
|
my ( $s, $min, $h, $d, $m, $y ) = (localtime)[ 0 .. 5 ];
|
||||||
|
$y += 1900; $m++; # usual adjustments
|
||||||
|
for ( $s, $min, $h, $d, $m ) {
|
||||||
|
$_ = "0$_" if $_ < 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
my ($template) = shift;
|
||||||
|
# substitute template parameters and set the logfile name
|
||||||
|
$template =~ s/%y/$y/g;
|
||||||
|
$template =~ s/%m/$m/g;
|
||||||
|
$template =~ s/%d/$d/g;
|
||||||
|
|
||||||
|
return $template;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub gl_log {
|
||||||
|
# the log filename and the timestamp come from the environment. If we get
|
||||||
|
# called even before they are set, we have no choice but to dump to STDERR
|
||||||
|
# (and probably call "logger").
|
||||||
|
|
||||||
|
# tab sep if there's more than one field
|
||||||
|
my $msg = join( "\t", @_ );
|
||||||
|
$msg =~ s/[\n\r]+/<<newline>>/g;
|
||||||
|
|
||||||
|
my $ts = gen_ts();
|
||||||
|
my $tid = $ENV{GL_TID} ||= $$;
|
||||||
|
|
||||||
|
my $fh;
|
||||||
|
logger_plus_stderr( "errors found before logging could be setup", "$msg" ) if not $ENV{GL_LOGFILE};
|
||||||
|
open my $lfh, ">>", $ENV{GL_LOGFILE}
|
||||||
|
or logger_plus_stderr( "errors found but logfile could not be created", "$ENV{GL_LOGFILE}: $!", "$msg" );
|
||||||
|
print $lfh "$ts\t$tid\t$msg\n";
|
||||||
|
close $lfh;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub logger_plus_stderr {
|
||||||
|
open my $fh, "|-", "logger" or confess "it's really not my day is it...?\n";
|
||||||
|
for ( @_ ) {
|
||||||
|
print STDERR "FATAL: $_\n";
|
||||||
|
print $fh "FATAL: $_\n";
|
||||||
|
}
|
||||||
|
exit 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
# bare-minimum subset of 'Tsh' (see github.com/sitaramc/tsh)
|
||||||
|
{
|
||||||
|
my ( $rc, $text );
|
||||||
|
sub tsh_rc { return $rc || 0; }
|
||||||
|
sub tsh_text { return $text || ''; }
|
||||||
|
sub tsh_lines { return split /\n/, $text; }
|
||||||
|
|
||||||
|
sub tsh_try {
|
||||||
|
my $cmd = shift; die "try: expects only one argument" if @_;
|
||||||
|
$text = `( $cmd ) 2>&1; printf RC=\$?`;
|
||||||
|
if ( $text =~ s/RC=(\d+)$// ) {
|
||||||
|
$rc = $1;
|
||||||
|
trace( 3, $text );
|
||||||
|
return ( not $rc );
|
||||||
|
}
|
||||||
|
die "couldnt find RC= in result; this should not happen:\n$text\n\n...\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
sub tsh_run {
|
||||||
|
open( my $fh, "-|", @_ ) or die "popen failed: $!";
|
||||||
|
local $/ = undef; $text = <$fh>;
|
||||||
|
close $fh; warn "pclose failed: $!" if $!;
|
||||||
|
$rc = ( $? >> 8 );
|
||||||
|
trace( 3, $text );
|
||||||
|
return $text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
1;
|
82
src/lib/Gitolite/Conf.pm
Normal file
82
src/lib/Gitolite/Conf.pm
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
package Gitolite::Conf;
|
||||||
|
|
||||||
|
# explode/parse a conf file
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
@EXPORT = qw(
|
||||||
|
compile
|
||||||
|
explode
|
||||||
|
parse
|
||||||
|
);
|
||||||
|
|
||||||
|
use Exporter 'import';
|
||||||
|
use Getopt::Long;
|
||||||
|
|
||||||
|
use Gitolite::Common;
|
||||||
|
use Gitolite::Rc;
|
||||||
|
use Gitolite::Conf::Sugar;
|
||||||
|
use Gitolite::Conf::Store;
|
||||||
|
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
sub compile {
|
||||||
|
_die "'gitolite compile' does not take any arguments" if @_;
|
||||||
|
|
||||||
|
_chdir( $rc{GL_ADMIN_BASE} );
|
||||||
|
_chdir("conf");
|
||||||
|
|
||||||
|
parse( sugar('gitolite.conf') );
|
||||||
|
|
||||||
|
# the order matters; new repos should be created first, to give store a
|
||||||
|
# place to put the individual gl-conf files
|
||||||
|
new_repos();
|
||||||
|
store();
|
||||||
|
|
||||||
|
for my $repo ( @{ $rc{NEW_REPOS_CREATED} } ) {
|
||||||
|
trigger( 'POST_CREATE', $repo );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub parse {
|
||||||
|
my $lines = shift;
|
||||||
|
trace( 2, scalar(@$lines) . " lines incoming" );
|
||||||
|
|
||||||
|
for my $line (@$lines) {
|
||||||
|
# user or repo groups
|
||||||
|
if ( $line =~ /^(@\S+) = (.*)/ ) {
|
||||||
|
add_to_group( $1, split( ' ', $2 ) );
|
||||||
|
} elsif ( $line =~ /^repo (.*)/ ) {
|
||||||
|
set_repolist( split( ' ', $1 ) );
|
||||||
|
} elsif ( $line =~ /^(-|C|R|RW\+?(?:C?D?|D?C?)M?) (.* )?= (.+)/ ) {
|
||||||
|
my $perm = $1;
|
||||||
|
my @refs = parse_refs( $2 || '' );
|
||||||
|
my @users = parse_users($3);
|
||||||
|
|
||||||
|
for my $ref (@refs) {
|
||||||
|
for my $user (@users) {
|
||||||
|
add_rule( $perm, $ref, $user );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} elsif ( $line =~ /^config (.+) = ?(.*)/ ) {
|
||||||
|
my ( $key, $value ) = ( $1, $2 );
|
||||||
|
$value =~ s/^['"](.*)["']$/$1/;
|
||||||
|
my @validkeys = split( ' ', ( $rc{GIT_CONFIG_KEYS} || '' ) );
|
||||||
|
push @validkeys, "gitolite-options\\..*";
|
||||||
|
my @matched = grep { $key =~ /^$_$/ } @validkeys;
|
||||||
|
_die "git config '$key' not allowed\ncheck GIT_CONFIG_KEYS in the rc file" if ( @matched < 1 );
|
||||||
|
_die "bad value '$value'" if $value =~ $UNSAFE_PATT;
|
||||||
|
add_config( 1, $key, $value );
|
||||||
|
} elsif ( $line =~ /^subconf (\S+)$/ ) {
|
||||||
|
trace( 2, $line );
|
||||||
|
set_subconf($1);
|
||||||
|
} else {
|
||||||
|
_warn "?? $line";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
parse_done();
|
||||||
|
}
|
||||||
|
|
||||||
|
1;
|
117
src/lib/Gitolite/Conf/Explode.pm
Normal file
117
src/lib/Gitolite/Conf/Explode.pm
Normal file
|
@ -0,0 +1,117 @@
|
||||||
|
package Gitolite::Conf::Explode;
|
||||||
|
|
||||||
|
# include/subconf processor
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
@EXPORT = qw(
|
||||||
|
explode
|
||||||
|
);
|
||||||
|
|
||||||
|
use Exporter 'import';
|
||||||
|
|
||||||
|
use Gitolite::Rc;
|
||||||
|
use Gitolite::Common;
|
||||||
|
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
# 'seen' for include/subconf files
|
||||||
|
my %included = ();
|
||||||
|
# 'seen' for group names on LHS
|
||||||
|
my %prefixed_groupname = ();
|
||||||
|
|
||||||
|
sub explode {
|
||||||
|
trace( 3, @_ );
|
||||||
|
my ( $file, $subconf, $out ) = @_;
|
||||||
|
|
||||||
|
# seed the 'seen' list if it's empty
|
||||||
|
$included{ device_inode("gitolite.conf") }++ unless %included;
|
||||||
|
|
||||||
|
my $fh = _open( "<", $file );
|
||||||
|
while (<$fh>) {
|
||||||
|
my $line = cleanup_conf_line($_);
|
||||||
|
next unless $line =~ /\S/;
|
||||||
|
|
||||||
|
# subst %HOSTNAME word if rc defines a hostname, else leave as is
|
||||||
|
$line =~ s/%HOSTNAME\b/$rc{HOSTNAME}/g if $rc{HOSTNAME};
|
||||||
|
|
||||||
|
$line = prefix_groupnames( $line, $subconf ) if $subconf ne 'master';
|
||||||
|
|
||||||
|
if ( $line =~ /^(include|subconf) (?:(\S+) )?(\S.+)$/ ) {
|
||||||
|
incsub( $1, $2, $3, $subconf, $out );
|
||||||
|
} else {
|
||||||
|
# normal line, send it to the callback function
|
||||||
|
push @{$out}, $line;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub incsub {
|
||||||
|
my $is_subconf = ( +shift eq 'subconf' );
|
||||||
|
my ( $new_subconf, $include_glob, $current_subconf, $out ) = @_;
|
||||||
|
|
||||||
|
_die "subconf '$current_subconf' attempting to run 'subconf'\n" if $is_subconf and $current_subconf ne 'master';
|
||||||
|
|
||||||
|
_die "invalid include/subconf file/glob '$include_glob'"
|
||||||
|
unless $include_glob =~ /^"(.+)"$/
|
||||||
|
or $include_glob =~ /^'(.+)'$/;
|
||||||
|
$include_glob = $1;
|
||||||
|
|
||||||
|
trace( 2, $is_subconf, $include_glob );
|
||||||
|
|
||||||
|
for my $file ( glob($include_glob) ) {
|
||||||
|
_warn("included file not found: '$file'"), next unless -f $file;
|
||||||
|
_die "invalid include/subconf filename '$file'" unless $file =~ m(([^/]+).conf$);
|
||||||
|
my $basename = $1;
|
||||||
|
|
||||||
|
next if already_included($file);
|
||||||
|
|
||||||
|
if ($is_subconf) {
|
||||||
|
push @{$out}, "subconf " . ( $new_subconf || $basename );
|
||||||
|
explode( $file, ( $new_subconf || $basename ), $out );
|
||||||
|
push @{$out}, "subconf $current_subconf";
|
||||||
|
} else {
|
||||||
|
explode( $file, $current_subconf, $out );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub prefix_groupnames {
|
||||||
|
my ( $line, $subconf ) = @_;
|
||||||
|
|
||||||
|
my $lhs = '';
|
||||||
|
# save 'foo' if it's an '@foo = list' line
|
||||||
|
$lhs = $1 if $line =~ /^@(\S+) = /;
|
||||||
|
# prefix all @groups in the line
|
||||||
|
$line =~ s/(^| )(@\S+)(?= |$)/ $1 . ($prefixed_groupname{$subconf}{$2} || $2) /ge;
|
||||||
|
# now prefix the LHS and store it if needed
|
||||||
|
if ($lhs) {
|
||||||
|
$line =~ s/^@\S+ = /"\@$subconf.$lhs = "/e;
|
||||||
|
$prefixed_groupname{$subconf}{"\@$lhs"} = "\@$subconf.$lhs";
|
||||||
|
trace( 3, "prefixed_groupname.$subconf.\@$lhs = \@$subconf.$lhs" );
|
||||||
|
}
|
||||||
|
|
||||||
|
return $line;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub already_included {
|
||||||
|
my $file = shift;
|
||||||
|
|
||||||
|
my $file_id = device_inode($file);
|
||||||
|
return 0 unless $included{$file_id}++;
|
||||||
|
|
||||||
|
_warn("$file already included");
|
||||||
|
trace( 2, "$file already included" );
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub device_inode {
|
||||||
|
my $file = shift;
|
||||||
|
trace( 3, $file, ( stat $file )[ 0, 1 ] );
|
||||||
|
return join( "/", ( stat $file )[ 0, 1 ] );
|
||||||
|
}
|
||||||
|
|
||||||
|
1;
|
||||||
|
|
595
src/lib/Gitolite/Conf/Load.pm
Normal file
595
src/lib/Gitolite/Conf/Load.pm
Normal file
|
@ -0,0 +1,595 @@
|
||||||
|
package Gitolite::Conf::Load;
|
||||||
|
|
||||||
|
# load conf data from stored files
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
@EXPORT = qw(
|
||||||
|
load
|
||||||
|
|
||||||
|
access
|
||||||
|
git_config
|
||||||
|
|
||||||
|
option
|
||||||
|
repo_missing
|
||||||
|
creator
|
||||||
|
|
||||||
|
vrefs
|
||||||
|
lister_dispatch
|
||||||
|
);
|
||||||
|
|
||||||
|
use Exporter 'import';
|
||||||
|
|
||||||
|
use Gitolite::Common;
|
||||||
|
use Gitolite::Rc;
|
||||||
|
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
# our variables, because they get loaded by a 'do'
|
||||||
|
our $data_version = '';
|
||||||
|
our %repos;
|
||||||
|
our %one_repo;
|
||||||
|
our %groups;
|
||||||
|
our %patterns;
|
||||||
|
our %configs;
|
||||||
|
our %one_config;
|
||||||
|
our %split_conf;
|
||||||
|
|
||||||
|
my $subconf = 'master';
|
||||||
|
|
||||||
|
my %listers = (
|
||||||
|
'list-groups' => \&list_groups,
|
||||||
|
'list-users' => \&list_users,
|
||||||
|
'list-repos' => \&list_repos,
|
||||||
|
'list-memberships' => \&list_memberships,
|
||||||
|
'list-members' => \&list_members,
|
||||||
|
);
|
||||||
|
|
||||||
|
# helps maintain the "cache" in both "load_common" and "load_1"
|
||||||
|
my $last_repo = '';
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
{
|
||||||
|
my $loaded_repo = '';
|
||||||
|
|
||||||
|
sub load {
|
||||||
|
my $repo = shift or _die "load() needs a reponame";
|
||||||
|
trace( 3, "$repo" );
|
||||||
|
if ( $repo ne $loaded_repo ) {
|
||||||
|
load_common();
|
||||||
|
load_1($repo);
|
||||||
|
$loaded_repo = $repo;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub access {
|
||||||
|
my ( $repo, $user, $aa, $ref ) = @_;
|
||||||
|
_die "invalid user '$user'" if not( $user and $user =~ $USERNAME_PATT );
|
||||||
|
sanity($repo);
|
||||||
|
|
||||||
|
my @rules;
|
||||||
|
my $deny_rules;
|
||||||
|
|
||||||
|
load($repo);
|
||||||
|
@rules = rules( $repo, $user );
|
||||||
|
$deny_rules = option( $repo, 'deny-rules' );
|
||||||
|
|
||||||
|
# sanity check the only piece the user can control
|
||||||
|
_die "invalid characters in ref or filename: '$ref'\n" unless $ref =~ $REF_OR_FILENAME_PATT;
|
||||||
|
|
||||||
|
# when a real repo doesn't exist, ^C is a pre-requisite for any other
|
||||||
|
# check to give valid results.
|
||||||
|
if ( $aa ne '^C' and $repo !~ /^\@/ and $repo =~ $REPONAME_PATT and repo_missing($repo) ) {
|
||||||
|
my $iret = access( $repo, $user, '^C', $ref );
|
||||||
|
$iret =~ s/\^C/$aa/;
|
||||||
|
return $iret if $iret =~ /DENIED/;
|
||||||
|
}
|
||||||
|
# similarly, ^C must be denied if the repo exists
|
||||||
|
if ( $aa eq '^C' and not repo_missing($repo) ) {
|
||||||
|
trace( 2, "DENIED by existence" );
|
||||||
|
return "$aa $ref $repo $user DENIED by existence";
|
||||||
|
}
|
||||||
|
|
||||||
|
trace( 2, scalar(@rules) . " rules found" );
|
||||||
|
for my $r (@rules) {
|
||||||
|
my $perm = $r->[1];
|
||||||
|
my $refex = $r->[2]; $refex =~ s(/USER/)(/$user/);
|
||||||
|
trace( 3, "perm=$perm, refex=$refex" );
|
||||||
|
|
||||||
|
# skip 'deny' rules if the ref is not (yet) known
|
||||||
|
next if $perm eq '-' and $ref eq 'any' and not $deny_rules;
|
||||||
|
|
||||||
|
# rule matches if ref matches or ref is any (see gitolite-shell)
|
||||||
|
next unless $ref =~ /^$refex/ or $ref eq 'any';
|
||||||
|
|
||||||
|
trace( 2, "DENIED by $refex" ) if $perm eq '-';
|
||||||
|
return "$aa $ref $repo $user DENIED by $refex" if $perm eq '-';
|
||||||
|
|
||||||
|
# $perm can be RW\+?(C|D|CD|DC)?M?. $aa can be W, +, C or D, or
|
||||||
|
# any of these followed by "M".
|
||||||
|
( my $aaq = $aa ) =~ s/\+/\\+/;
|
||||||
|
$aaq =~ s/M/.*M/;
|
||||||
|
# as far as *this* ref is concerned we're ok
|
||||||
|
return $refex if ( $perm =~ /$aaq/ );
|
||||||
|
}
|
||||||
|
trace( 2, "DENIED by fallthru" );
|
||||||
|
return "$aa $ref $repo $user DENIED by fallthru";
|
||||||
|
}
|
||||||
|
|
||||||
|
sub git_config {
|
||||||
|
my ( $repo, $key, $empty_values_OK ) = @_;
|
||||||
|
$key ||= '.';
|
||||||
|
|
||||||
|
if (repo_missing($repo)) {
|
||||||
|
load_common();
|
||||||
|
} else {
|
||||||
|
load($repo);
|
||||||
|
}
|
||||||
|
|
||||||
|
# read comments bottom up
|
||||||
|
my %ret =
|
||||||
|
# and take the second and third elements to make up your new hash
|
||||||
|
map { $_->[1] => $_->[2] }
|
||||||
|
# keep only the ones where the second element matches your key
|
||||||
|
grep { $_->[1] =~ qr($key) }
|
||||||
|
# sort this list of listrefs by the first element in each list ref'd to
|
||||||
|
sort { $a->[0] <=> $b->[0] }
|
||||||
|
# dereference it (into a list of listrefs)
|
||||||
|
map { @$_ }
|
||||||
|
# take the value of that entry
|
||||||
|
map { $configs{$_} }
|
||||||
|
# if it has an entry in %configs
|
||||||
|
grep { $configs{$_} }
|
||||||
|
# for each "repo" that represents us
|
||||||
|
memberships( 'repo', $repo );
|
||||||
|
|
||||||
|
# %configs looks like this (for each 'foo' that is in memberships())
|
||||||
|
# 'foo' => [ [ 6, 'foo.bar', 'repo' ], [ 7, 'foodbar', 'repoD' ], [ 8, 'foo.czar', 'jule' ] ],
|
||||||
|
# the first map gets you the value
|
||||||
|
# [ [ 6, 'foo.bar', 'repo' ], [ 7, 'foodbar', 'repoD' ], [ 8, 'foo.czar', 'jule' ] ],
|
||||||
|
# the deref gets you
|
||||||
|
# [ 6, 'foo.bar', 'repo' ], [ 7, 'foodbar', 'repoD' ], [ 8, 'foo.czar', 'jule' ]
|
||||||
|
# the sort rearranges it (in this case it's already sorted but anyway...)
|
||||||
|
# the grep gets you this, assuming the key is foo.bar (and "." is regex ".')
|
||||||
|
# [ 6, 'foo.bar', 'repo' ], [ 7, 'foodbar', 'repoD' ]
|
||||||
|
# and the final map does this:
|
||||||
|
# 'foo.bar'=>'repo' , 'foodbar'=>'repoD'
|
||||||
|
|
||||||
|
# now some of these will have an empty key; we need to delete them unless
|
||||||
|
# we're told empty values are OK
|
||||||
|
unless ($empty_values_OK) {
|
||||||
|
my($k, $v);
|
||||||
|
while (($k, $v) = each %ret) {
|
||||||
|
delete $ret{$k} if not $v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
my($k, $v);
|
||||||
|
my $creator = creator($repo);
|
||||||
|
while (($k, $v) = each %ret) {
|
||||||
|
$v =~ s/%GL_REPO/$repo/g;
|
||||||
|
$v =~ s/%GL_CREATOR/$creator/g if $creator;
|
||||||
|
$ret{$k} = $v;
|
||||||
|
}
|
||||||
|
|
||||||
|
trace( 3, map { ( "$_" => "-> $ret{$_}" ) } ( sort keys %ret ) );
|
||||||
|
return \%ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub option {
|
||||||
|
my ( $repo, $option ) = @_;
|
||||||
|
$option = "gitolite-options.$option";
|
||||||
|
my $ret = git_config( $repo, "^\Q$option\E\$" );
|
||||||
|
return '' unless %$ret;
|
||||||
|
return $ret->{$option};
|
||||||
|
}
|
||||||
|
|
||||||
|
sub sanity {
|
||||||
|
my $repo = shift;
|
||||||
|
|
||||||
|
_die "invalid repo '$repo'" if not( $repo and $repo =~ $REPOPATT_PATT );
|
||||||
|
_die "'$repo' ends with a '/'" if $repo =~ m(/$);
|
||||||
|
_die "'$repo' contains '..'" if $repo =~ $REPONAME_PATT and $repo =~ m(\.\.);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub repo_missing {
|
||||||
|
my $repo = shift;
|
||||||
|
sanity($repo);
|
||||||
|
|
||||||
|
return not -d "$rc{GL_REPO_BASE}/$repo.git";
|
||||||
|
}
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
sub load_common {
|
||||||
|
|
||||||
|
_chdir( $rc{GL_ADMIN_BASE} );
|
||||||
|
|
||||||
|
# we take an unusual approach to caching this function!
|
||||||
|
# (requires that first call to load_common is before first call to load_1)
|
||||||
|
if ( $last_repo and $split_conf{$last_repo} ) {
|
||||||
|
delete $repos{$last_repo};
|
||||||
|
delete $configs{$last_repo};
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
my $cc = "conf/gitolite.conf-compiled.pm";
|
||||||
|
|
||||||
|
_die "parse '$cc' failed: " . ( $! or $@ ) unless do $cc;
|
||||||
|
|
||||||
|
if ( data_version_mismatch() ) {
|
||||||
|
_system("gitolite setup");
|
||||||
|
_die "parse '$cc' failed: " . ( $! or $@ ) unless do $cc;
|
||||||
|
_die "data version update failed; this is serious" if data_version_mismatch();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub load_1 {
|
||||||
|
my $repo = shift;
|
||||||
|
return if $repo =~ /^\@/;
|
||||||
|
trace( 3, $repo );
|
||||||
|
|
||||||
|
if ( repo_missing($repo) ) {
|
||||||
|
trace( 1, "repo '$repo' missing" ) if $repo =~ $REPONAME_PATT;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_chdir("$rc{GL_REPO_BASE}/$repo.git");
|
||||||
|
|
||||||
|
if ( $repo eq $last_repo ) {
|
||||||
|
$repos{$repo} = $one_repo{$repo};
|
||||||
|
$configs{$repo} = $one_config{$repo} if $one_config{$repo};
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( -f "gl-conf" ) {
|
||||||
|
return if not $split_conf{$repo};
|
||||||
|
|
||||||
|
my $cc = "./gl-conf";
|
||||||
|
_die "parse '$cc' failed: " . ( $! or $@ ) unless do $cc;
|
||||||
|
|
||||||
|
$last_repo = $repo;
|
||||||
|
$repos{$repo} = $one_repo{$repo};
|
||||||
|
$configs{$repo} = $one_config{$repo} if $one_config{$repo};
|
||||||
|
} else {
|
||||||
|
_die "split conf set, gl-conf not present for '$repo'" if $split_conf{$repo};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
my $lastrepo = '';
|
||||||
|
my $lastuser = '';
|
||||||
|
my @cached = ();
|
||||||
|
|
||||||
|
sub rules {
|
||||||
|
my ( $repo, $user ) = @_;
|
||||||
|
trace( 3, "repo=$repo, user=$user" );
|
||||||
|
|
||||||
|
return @cached if ( $lastrepo eq $repo and $lastuser eq $user and @cached );
|
||||||
|
|
||||||
|
my @rules = ();
|
||||||
|
|
||||||
|
my @repos = memberships( 'repo', $repo );
|
||||||
|
my @users = memberships( 'user', $user, $repo );
|
||||||
|
trace( 3, "memberships: " . scalar(@repos) . " repos and " . scalar(@users) . " users found" );
|
||||||
|
|
||||||
|
for my $r (@repos) {
|
||||||
|
for my $u (@users) {
|
||||||
|
push @rules, @{ $repos{$r}{$u} } if exists $repos{$r} and exists $repos{$r}{$u};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@rules = sort { $a->[0] <=> $b->[0] } @rules;
|
||||||
|
|
||||||
|
$lastrepo = $repo;
|
||||||
|
$lastuser = $user;
|
||||||
|
@cached = @rules;
|
||||||
|
|
||||||
|
# however if the repo was missing, invalidate the cache
|
||||||
|
$lastrepo = '' if repo_missing($repo);
|
||||||
|
|
||||||
|
return @rules;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub vrefs {
|
||||||
|
my ( $repo, $user ) = @_;
|
||||||
|
# fill the cache if needed
|
||||||
|
rules( $repo, $user ) unless ( $lastrepo eq $repo and $lastuser eq $user and @cached );
|
||||||
|
|
||||||
|
my %seen;
|
||||||
|
my @vrefs = grep { /^VREF\// and not $seen{$_}++ } map { $_->[2] } @cached;
|
||||||
|
return @vrefs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub memberships {
|
||||||
|
trace( 3, @_ );
|
||||||
|
my ( $type, $base, $repo ) = @_;
|
||||||
|
$repo ||= '';
|
||||||
|
my @ret;
|
||||||
|
my $base2 = '';
|
||||||
|
|
||||||
|
@ret = ( $base, '@all' );
|
||||||
|
|
||||||
|
if ( $type eq 'repo' ) {
|
||||||
|
# first, if a repo, say, pub/sitaram/project, has a gl-creator file
|
||||||
|
# that says "sitaram", find memberships for pub/CREATOR/project also
|
||||||
|
$base2 = generic_name($base);
|
||||||
|
|
||||||
|
# second, you need to check in %repos also
|
||||||
|
for my $i ( keys %repos, keys %configs ) {
|
||||||
|
if ( $base eq $i or $base =~ /^$i$/ or $base2 and ( $base2 eq $i or $base2 =~ /^$i$/ ) ) {
|
||||||
|
push @ret, $i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
push @ret, @{ $groups{$base} } if exists $groups{$base};
|
||||||
|
push @ret, @{ $groups{$base2} } if $base2 and exists $groups{$base2};
|
||||||
|
for my $i ( keys %{ $patterns{groups} } ) {
|
||||||
|
if ( $base =~ /^$i$/ or $base2 and ( $base2 =~ /^$i$/ ) ) {
|
||||||
|
push @ret, @{ $groups{$i} };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
push @ret, @{ ext_grouplist($base) } if $type eq 'user' and $rc{GROUPLIST_PGM};
|
||||||
|
|
||||||
|
if ( $type eq 'user' and $repo and not repo_missing($repo) ) {
|
||||||
|
# find the roles this user has when accessing this repo and add those
|
||||||
|
# in as groupnames he is a member of. You need the already existing
|
||||||
|
# memberships for this; see below this function for an example
|
||||||
|
push @ret, user_roles( $base, $repo, @ret );
|
||||||
|
}
|
||||||
|
|
||||||
|
@ret = @{ sort_u( \@ret ) };
|
||||||
|
trace( 3, sort @ret );
|
||||||
|
return @ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
=for example
|
||||||
|
|
||||||
|
conf/gitolite.conf:
|
||||||
|
@g1 = u1
|
||||||
|
@g2 = u1
|
||||||
|
# now user is a member of both g1 and g2
|
||||||
|
|
||||||
|
gl-perms for repo being accessed:
|
||||||
|
READERS @g1
|
||||||
|
|
||||||
|
This should result in @READERS being added to the memberships that u1 has
|
||||||
|
(when accessing this repo). So we send the current list (@g1, @g2) to
|
||||||
|
user_roles(), otherwise it has to redo that logic.
|
||||||
|
|
||||||
|
=cut
|
||||||
|
|
||||||
|
sub data_version_mismatch {
|
||||||
|
return $data_version ne glrc('current-data-version');
|
||||||
|
}
|
||||||
|
|
||||||
|
sub user_roles {
|
||||||
|
my ( $user, $repo, @eg ) = @_;
|
||||||
|
|
||||||
|
# eg == existing groups (that user is already known to be a member of)
|
||||||
|
my %eg = map { $_ => 1 } @eg;
|
||||||
|
|
||||||
|
my %ret = ();
|
||||||
|
my $f = "$rc{GL_REPO_BASE}/$repo.git/gl-perms";
|
||||||
|
my @roles = ();
|
||||||
|
if ( -f $f ) {
|
||||||
|
my $fh = _open( "<", $f );
|
||||||
|
chomp( @roles = <$fh> );
|
||||||
|
}
|
||||||
|
push @roles, "CREATOR = " . creator($repo);
|
||||||
|
for (@roles) {
|
||||||
|
# READERS u3 u4 @g1
|
||||||
|
s/^\s+//; s/ +$//; s/=/ /; s/\s+/ /g; s/^\@//;
|
||||||
|
next if /^#/;
|
||||||
|
next unless /\S/;
|
||||||
|
my ( $role, @members ) = split;
|
||||||
|
# role = READERS, members = u3, u4, @g1
|
||||||
|
if ( $role ne 'CREATOR' and not $rc{ROLES}{$role} ) {
|
||||||
|
_warn "role '$role' not allowed, ignoring";
|
||||||
|
next;
|
||||||
|
}
|
||||||
|
for my $m (@members) {
|
||||||
|
if ( $m !~ $USERNAME_PATT ) {
|
||||||
|
_warn "ignoring '$m' in perms line";
|
||||||
|
next;
|
||||||
|
}
|
||||||
|
# if user eq u3/u4, or is a member of @g1, he has role READERS
|
||||||
|
$ret{ '@' . $role } = 1 if $m eq $user or $eg{$m};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return keys %ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub generic_name {
|
||||||
|
my $base = shift;
|
||||||
|
my $base2 = '';
|
||||||
|
my $creator;
|
||||||
|
|
||||||
|
# get the creator name. For not-yet-born repos this is $ENV{GL_USER},
|
||||||
|
# which should be set in all cases that we care about, viz., where we are
|
||||||
|
# checking ^C permissions before new_wild_repo(), and the info command.
|
||||||
|
# In particular, 'gitolite access' can't be used to check ^C perms on wild
|
||||||
|
# repos that contain "CREATOR" if GL_USER is not set.
|
||||||
|
$creator = creator($base);
|
||||||
|
|
||||||
|
$base2 = $base;
|
||||||
|
$base2 =~ s(\b$creator\b)(CREATOR) if $creator;
|
||||||
|
$base2 = '' if $base2 eq $base; # if there was no change
|
||||||
|
|
||||||
|
return $base2;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub creator {
|
||||||
|
my $repo = shift;
|
||||||
|
sanity($repo);
|
||||||
|
|
||||||
|
return ( $ENV{GL_USER} || '' ) if repo_missing($repo);
|
||||||
|
my $f = "$rc{GL_REPO_BASE}/$repo.git/gl-creator";
|
||||||
|
my $creator = '';
|
||||||
|
chomp( $creator = slurp($f) ) if -f $f;
|
||||||
|
return $creator;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
my %cache = ();
|
||||||
|
|
||||||
|
sub ext_grouplist {
|
||||||
|
my $user = shift;
|
||||||
|
my $pgm = $rc{GROUPLIST_PGM};
|
||||||
|
return [] if not $pgm;
|
||||||
|
|
||||||
|
return $cache{$user} if $cache{$user};
|
||||||
|
my @extgroups = map { s/^@?/@/; $_; } split ' ', `$rc{GROUPLIST_PGM} $user`;
|
||||||
|
return ( $cache{$user} = \@extgroups );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
# api functions
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
sub lister_dispatch {
|
||||||
|
my $command = shift;
|
||||||
|
|
||||||
|
my $fn = $listers{$command} or _die "unknown gitolite sub-command";
|
||||||
|
return $fn;
|
||||||
|
}
|
||||||
|
|
||||||
|
=for list_groups
|
||||||
|
Usage: gitolite list-groups
|
||||||
|
|
||||||
|
- lists all group names in conf
|
||||||
|
- no options, no flags
|
||||||
|
=cut
|
||||||
|
|
||||||
|
sub list_groups {
|
||||||
|
usage() if @_;
|
||||||
|
|
||||||
|
load_common();
|
||||||
|
|
||||||
|
my @g = ();
|
||||||
|
while ( my ( $k, $v ) = each(%groups) ) {
|
||||||
|
push @g, @{$v};
|
||||||
|
}
|
||||||
|
return ( sort_u( \@g ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
=for list_users
|
||||||
|
Usage: gitolite list-users [<repo name pattern>]
|
||||||
|
|
||||||
|
List all users and groups explicitly named in a rule. User names not
|
||||||
|
mentioned in an access rule will not show up; you have to run 'list-members'
|
||||||
|
on each group name yourself to see them.
|
||||||
|
|
||||||
|
WARNING: may be slow if you have thousands of repos. The optional repo name
|
||||||
|
pattern is an unanchored regex; it can speed things up if you're interested
|
||||||
|
only in users of a matching set of repos. This is only an optimisation, not
|
||||||
|
an actual access list; you will still have to pipe it to 'gitolite access'
|
||||||
|
with appropriate arguments to get an actual access list.
|
||||||
|
=cut
|
||||||
|
|
||||||
|
sub list_users {
|
||||||
|
my $patt = shift || '.';
|
||||||
|
usage() if $patt eq '-h' or @_;
|
||||||
|
my $count = 0;
|
||||||
|
my $total = 0;
|
||||||
|
|
||||||
|
load_common();
|
||||||
|
|
||||||
|
my @u = map { keys %{$_} } values %repos;
|
||||||
|
$total = scalar( grep { /$patt/ } keys %split_conf );
|
||||||
|
warn "WARNING: you have $total repos to check; this could take some time!\n" if $total > 100;
|
||||||
|
for my $one ( grep { /$patt/ } keys %split_conf ) {
|
||||||
|
load_1($one);
|
||||||
|
$count++; print STDERR "$count / $total\r" if not( $count % 100 ) and timer(5);
|
||||||
|
push @u, map { keys %{$_} } values %one_repo;
|
||||||
|
}
|
||||||
|
print STDERR "\n" if $count >= 100;
|
||||||
|
return ( sort_u( \@u ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
=for list_repos
|
||||||
|
Usage: gitolite list-repos
|
||||||
|
|
||||||
|
- lists all repos/repo groups in conf
|
||||||
|
- no options, no flags
|
||||||
|
=cut
|
||||||
|
|
||||||
|
sub list_repos {
|
||||||
|
usage() if @_;
|
||||||
|
|
||||||
|
load_common();
|
||||||
|
|
||||||
|
my @r = keys %repos;
|
||||||
|
push @r, keys %split_conf;
|
||||||
|
|
||||||
|
return ( sort_u( \@r ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
=for list_memberships
|
||||||
|
Usage: gitolite list-memberships <name>
|
||||||
|
|
||||||
|
- list all groups a name is a member of
|
||||||
|
- takes one user/repo name
|
||||||
|
=cut
|
||||||
|
|
||||||
|
sub list_memberships {
|
||||||
|
usage() if @_ and $_[0] eq '-h' or not @_;
|
||||||
|
|
||||||
|
my $name = shift;
|
||||||
|
|
||||||
|
load_common();
|
||||||
|
my @m = memberships( '', $name );
|
||||||
|
return ( sort_u( \@m ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
=for list_members
|
||||||
|
Usage: gitolite list-members <group name>
|
||||||
|
|
||||||
|
- list all members of a group
|
||||||
|
- takes one group name
|
||||||
|
=cut
|
||||||
|
|
||||||
|
sub list_members {
|
||||||
|
usage() if @_ and $_[0] eq '-h' or not @_;
|
||||||
|
|
||||||
|
my $name = shift;
|
||||||
|
|
||||||
|
load_common();
|
||||||
|
|
||||||
|
my @m = ();
|
||||||
|
while ( my ( $k, $v ) = each(%groups) ) {
|
||||||
|
for my $g ( @{$v} ) {
|
||||||
|
push @m, $k if $g eq $name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ( sort_u( \@m ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
{
|
||||||
|
my $start_time = 0;
|
||||||
|
|
||||||
|
sub timer {
|
||||||
|
unless ($start_time) {
|
||||||
|
$start_time = time();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
my $elapsed = shift;
|
||||||
|
return 0 if time() - $start_time < $elapsed;
|
||||||
|
$start_time = time();
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
1;
|
||||||
|
|
368
src/lib/Gitolite/Conf/Store.pm
Normal file
368
src/lib/Gitolite/Conf/Store.pm
Normal file
|
@ -0,0 +1,368 @@
|
||||||
|
package Gitolite::Conf::Store;
|
||||||
|
|
||||||
|
# receive parsed conf data and store it
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
@EXPORT = qw(
|
||||||
|
add_to_group
|
||||||
|
set_repolist
|
||||||
|
parse_refs
|
||||||
|
parse_users
|
||||||
|
add_rule
|
||||||
|
add_config
|
||||||
|
set_subconf
|
||||||
|
|
||||||
|
expand_list
|
||||||
|
new_repos
|
||||||
|
new_repo
|
||||||
|
new_wild_repo
|
||||||
|
hook_repos
|
||||||
|
store
|
||||||
|
parse_done
|
||||||
|
);
|
||||||
|
|
||||||
|
use Exporter 'import';
|
||||||
|
use Data::Dumper;
|
||||||
|
$Data::Dumper::Indent = 1;
|
||||||
|
$Data::Dumper::Sortkeys = 1;
|
||||||
|
|
||||||
|
use Gitolite::Common;
|
||||||
|
use Gitolite::Rc;
|
||||||
|
use Gitolite::Hooks::Update;
|
||||||
|
use Gitolite::Hooks::PostUpdate;
|
||||||
|
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
my %repos;
|
||||||
|
my %groups;
|
||||||
|
my %configs;
|
||||||
|
my %split_conf;
|
||||||
|
|
||||||
|
my @repolist; # current repo list; reset on each 'repo ...' line
|
||||||
|
my $subconf = 'master';
|
||||||
|
my $nextseq = 0;
|
||||||
|
my %ignored;
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
sub add_to_group {
|
||||||
|
my ( $lhs, @rhs ) = @_;
|
||||||
|
_die "bad group '$lhs'" unless $lhs =~ $REPONAME_PATT;
|
||||||
|
map { _die "bad expansion '$_'" unless $_ =~ $REPOPATT_PATT } @rhs;
|
||||||
|
|
||||||
|
# store the group association, but overload it to keep track of when
|
||||||
|
# the group was *first* created by using $subconf as the *value*
|
||||||
|
do { $groups{$lhs}{$_} ||= $subconf }
|
||||||
|
for ( expand_list(@rhs) );
|
||||||
|
|
||||||
|
# create the group hash even if empty
|
||||||
|
$groups{$lhs} = {} unless $groups{$lhs};
|
||||||
|
}
|
||||||
|
|
||||||
|
sub set_repolist {
|
||||||
|
|
||||||
|
@repolist = ();
|
||||||
|
# ...sanity checks
|
||||||
|
for (@_) {
|
||||||
|
if ( check_subconf_repo_disallowed( $subconf, $_ ) ) {
|
||||||
|
(my $repo = $_) =~ s/^\@$subconf\./locally modified \@/;
|
||||||
|
$ignored{$subconf}{$repo} = 1;
|
||||||
|
next;
|
||||||
|
}
|
||||||
|
|
||||||
|
_warn "explicit '.git' extension ignored for $_.git" if s/\.git$//;
|
||||||
|
_die "bad reponame '$_'" if $_ !~ $REPOPATT_PATT;
|
||||||
|
|
||||||
|
push @repolist, $_;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub parse_refs {
|
||||||
|
my $refs = shift;
|
||||||
|
my @refs; @refs = split( ' ', $refs ) if $refs;
|
||||||
|
@refs = expand_list(@refs);
|
||||||
|
|
||||||
|
# if no ref is given, this PERM applies to all refs
|
||||||
|
@refs = qw(refs/.*) unless @refs;
|
||||||
|
|
||||||
|
# fully qualify refs that dont start with "refs/" or "VREF/";
|
||||||
|
# prefix them with "refs/heads/"
|
||||||
|
@refs = map { m(^(refs|VREF)/) or s(^)(refs/heads/); $_ } @refs;
|
||||||
|
|
||||||
|
return @refs;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub parse_users {
|
||||||
|
my $users = shift;
|
||||||
|
my @users = split ' ', $users;
|
||||||
|
do { _die "bad username '$_'" unless $_ =~ $USERNAME_PATT }
|
||||||
|
for @users;
|
||||||
|
|
||||||
|
return @users;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub add_rule {
|
||||||
|
my ( $perm, $ref, $user ) = @_;
|
||||||
|
_die "bad ref '$ref'" unless $ref =~ $REPOPATT_PATT;
|
||||||
|
_die "bad user '$user'" unless $user =~ $USERNAME_PATT;
|
||||||
|
|
||||||
|
$nextseq++;
|
||||||
|
for my $repo (@repolist) {
|
||||||
|
push @{ $repos{$repo}{$user} }, [ $nextseq, $perm, $ref ];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub add_config {
|
||||||
|
my ( $n, $key, $value ) = @_;
|
||||||
|
|
||||||
|
$nextseq++;
|
||||||
|
for my $repo (@repolist) {
|
||||||
|
push @{ $configs{$repo} }, [ $nextseq, $key, $value ];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub set_subconf {
|
||||||
|
$subconf = shift;
|
||||||
|
_die "bad subconf '$subconf'" unless $subconf =~ /^[-\w.]+$/;
|
||||||
|
}
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
sub expand_list {
|
||||||
|
my @list = @_;
|
||||||
|
my @new_list = ();
|
||||||
|
|
||||||
|
for my $item (@list) {
|
||||||
|
if ( $item =~ /^@/ and $item ne '@all' ) # nested group
|
||||||
|
{
|
||||||
|
_die "undefined group '$item'" unless $groups{$item};
|
||||||
|
# add those names to the list
|
||||||
|
push @new_list, sort keys %{ $groups{$item} };
|
||||||
|
} else {
|
||||||
|
push @new_list, $item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return @new_list;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub new_repos {
|
||||||
|
trace(3);
|
||||||
|
_chdir( $rc{GL_REPO_BASE} );
|
||||||
|
|
||||||
|
# normal repos
|
||||||
|
my @repos = grep { $_ =~ $REPONAME_PATT and not /^@/ } sort keys %repos;
|
||||||
|
# add in members of repo groups
|
||||||
|
map { push @repos, keys %{ $groups{$_} } } grep { /^@/ and $_ ne '@all' } keys %repos;
|
||||||
|
|
||||||
|
for my $repo ( @{ sort_u( \@repos ) } ) {
|
||||||
|
next unless $repo =~ $REPONAME_PATT; # skip repo patterns
|
||||||
|
next if $repo =~ m(^\@|EXTCMD/); # skip groups and fake repos
|
||||||
|
|
||||||
|
# use gl-conf as a sentinel
|
||||||
|
hook_1($repo) if -d "$repo.git" and not -f "$repo.git/gl-conf";
|
||||||
|
|
||||||
|
if (not -d "$repo.git") {
|
||||||
|
push @{ $rc{NEW_REPOS_CREATED} }, $repo;
|
||||||
|
trigger( 'PRE_CREATE', $repo );
|
||||||
|
new_repo($repo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub new_repo {
|
||||||
|
my $repo = shift;
|
||||||
|
trace( 3, $repo );
|
||||||
|
|
||||||
|
_mkdir("$repo.git");
|
||||||
|
_chdir("$repo.git");
|
||||||
|
_system("git init --bare >&2");
|
||||||
|
_chdir( $rc{GL_REPO_BASE} );
|
||||||
|
hook_1($repo);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub new_wild_repo {
|
||||||
|
my ( $repo, $user, $aa ) = @_;
|
||||||
|
_chdir( $rc{GL_REPO_BASE} );
|
||||||
|
|
||||||
|
trigger( 'PRE_CREATE', $repo, $user, $aa );
|
||||||
|
new_repo($repo);
|
||||||
|
_print( "$repo.git/gl-creator", $user );
|
||||||
|
_print( "$repo.git/gl-perms", ( $rc{DEFAULT_ROLE_PERMS} ? "$rc{DEFAULT_ROLE_PERMS}\n" : "" ) );
|
||||||
|
trigger( 'POST_CREATE', $repo, $user, $aa );
|
||||||
|
|
||||||
|
_chdir( $rc{GL_ADMIN_BASE} );
|
||||||
|
}
|
||||||
|
|
||||||
|
sub hook_repos {
|
||||||
|
trace(3);
|
||||||
|
# all repos, all hooks
|
||||||
|
_chdir( $rc{GL_REPO_BASE} );
|
||||||
|
|
||||||
|
for my $repo (`find . -name "*.git" -prune`) {
|
||||||
|
chomp($repo);
|
||||||
|
$repo =~ s/\.git$//;
|
||||||
|
$repo =~ s(^\./)();
|
||||||
|
hook_1($repo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub store {
|
||||||
|
trace(3);
|
||||||
|
|
||||||
|
# first write out the ones for the physical repos
|
||||||
|
_chdir( $rc{GL_REPO_BASE} );
|
||||||
|
my $phy_repos = list_phy_repos(1);
|
||||||
|
|
||||||
|
for my $repo ( @{$phy_repos} ) {
|
||||||
|
store_1($repo);
|
||||||
|
}
|
||||||
|
|
||||||
|
_chdir( $rc{GL_ADMIN_BASE} );
|
||||||
|
store_common();
|
||||||
|
}
|
||||||
|
|
||||||
|
sub parse_done {
|
||||||
|
for my $ig ( sort keys %ignored ) {
|
||||||
|
_warn "subconf '$ig' attempting to set access for " . join( ", ", sort keys %{ $ignored{$ig} } );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
sub check_subconf_repo_disallowed {
|
||||||
|
# trying to set access for $repo (='foo')...
|
||||||
|
my ( $subconf, $repo ) = @_;
|
||||||
|
trace( 2, $subconf, $repo );
|
||||||
|
|
||||||
|
# processing the master config, not a subconf
|
||||||
|
return 0 if $subconf eq 'master';
|
||||||
|
# subconf is also called 'foo' (you're allowed to have a
|
||||||
|
# subconf that is only concerned with one repo)
|
||||||
|
return 0 if $subconf eq $repo;
|
||||||
|
# same thing in big-config-land; foo is just @foo now
|
||||||
|
return 0 if ( "\@$subconf" eq $repo );
|
||||||
|
my @matched = grep { $repo =~ /^$_$/ }
|
||||||
|
grep { $groups{"\@$subconf"}{$_} eq 'master' }
|
||||||
|
sort keys %{ $groups{"\@$subconf"} };
|
||||||
|
return 0 if @matched > 0;
|
||||||
|
|
||||||
|
trace( 2, "-> disallowed" );
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub store_1 {
|
||||||
|
# warning: writes and *deletes* it from %repos and %configs
|
||||||
|
my ($repo) = shift;
|
||||||
|
trace( 3, $repo );
|
||||||
|
return unless ( $repos{$repo} or $configs{$repo} ) and -d "$repo.git";
|
||||||
|
|
||||||
|
my ( %one_repo, %one_config );
|
||||||
|
|
||||||
|
open( my $compiled_fh, ">", "$repo.git/gl-conf" ) or return;
|
||||||
|
|
||||||
|
my $dumped_data = '';
|
||||||
|
if ($repos{$repo}) {
|
||||||
|
$one_repo{$repo} = $repos{$repo};
|
||||||
|
delete $repos{$repo};
|
||||||
|
$dumped_data = Data::Dumper->Dump( [ \%one_repo ], [qw(*one_repo)] );
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( $configs{$repo} ) {
|
||||||
|
$one_config{$repo} = $configs{$repo};
|
||||||
|
delete $configs{$repo};
|
||||||
|
$dumped_data .= Data::Dumper->Dump( [ \%one_config ], [qw(*one_config)] );
|
||||||
|
}
|
||||||
|
|
||||||
|
print $compiled_fh $dumped_data;
|
||||||
|
close $compiled_fh;
|
||||||
|
|
||||||
|
$split_conf{$repo} = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub store_common {
|
||||||
|
trace(3);
|
||||||
|
my $cc = "conf/gitolite.conf-compiled.pm";
|
||||||
|
my $compiled_fh = _open( ">", "$cc.new" );
|
||||||
|
|
||||||
|
my %patterns = ();
|
||||||
|
|
||||||
|
my $data_version = glrc('current-data-version');
|
||||||
|
trace( 3, "data_version = $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( [ \%configs ], [qw(*configs)] ) if %configs;
|
||||||
|
|
||||||
|
print $compiled_fh $dumped_data;
|
||||||
|
|
||||||
|
if (%groups) {
|
||||||
|
my %groups = %{ inside_out( \%groups ) };
|
||||||
|
$dumped_data = Data::Dumper->Dump( [ \%groups ], [qw(*groups)] );
|
||||||
|
print $compiled_fh $dumped_data;
|
||||||
|
|
||||||
|
# save patterns in %groups for faster handling of multiple repos, such
|
||||||
|
# as happens in the various POST_COMPILE scripts
|
||||||
|
for my $k (keys %groups) {
|
||||||
|
$patterns{groups}{$k} = 1 unless $k =~ $REPONAME_PATT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
print $compiled_fh Data::Dumper->Dump( [ \%patterns ], [qw(*patterns)] ) if %patterns;
|
||||||
|
|
||||||
|
print $compiled_fh Data::Dumper->Dump( [ \%split_conf ], [qw(*split_conf)] ) if %split_conf;
|
||||||
|
|
||||||
|
close $compiled_fh or _die "close compiled-conf failed: $!\n";
|
||||||
|
rename "$cc.new", $cc;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
my $hook_reset = 0;
|
||||||
|
|
||||||
|
sub hook_1 {
|
||||||
|
my $repo = shift;
|
||||||
|
trace( 3, $repo );
|
||||||
|
|
||||||
|
# reset the gitolite supplied hooks, in case someone fiddled with
|
||||||
|
# them, but only once per run
|
||||||
|
if ( not $hook_reset ) {
|
||||||
|
_mkdir("$rc{GL_ADMIN_BASE}/hooks/common");
|
||||||
|
_mkdir("$rc{GL_ADMIN_BASE}/hooks/gitolite-admin");
|
||||||
|
_print( "$rc{GL_ADMIN_BASE}/hooks/common/update", update_hook() );
|
||||||
|
_print( "$rc{GL_ADMIN_BASE}/hooks/gitolite-admin/post-update", post_update_hook() );
|
||||||
|
chmod 0755, "$rc{GL_ADMIN_BASE}/hooks/common/update";
|
||||||
|
chmod 0755, "$rc{GL_ADMIN_BASE}/hooks/gitolite-admin/post-update";
|
||||||
|
$hook_reset++;
|
||||||
|
}
|
||||||
|
|
||||||
|
# propagate user-defined (custom) hooks to all repos
|
||||||
|
ln_sf( "$rc{LOCAL_CODE}/hooks/common", "*", "$repo.git/hooks" ) if $rc{LOCAL_CODE};
|
||||||
|
|
||||||
|
# override/propagate gitolite defined hooks for all repos
|
||||||
|
ln_sf( "$rc{GL_ADMIN_BASE}/hooks/common", "*", "$repo.git/hooks" );
|
||||||
|
# override/propagate gitolite defined hooks for the admin repo
|
||||||
|
ln_sf( "$rc{GL_ADMIN_BASE}/hooks/gitolite-admin", "*", "$repo.git/hooks" ) if $repo eq 'gitolite-admin';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub inside_out {
|
||||||
|
my $href = shift;
|
||||||
|
# input conf: @aa = bb cc <newline> @bb = @aa dd
|
||||||
|
|
||||||
|
my %ret = ();
|
||||||
|
while ( my ( $k, $v ) = each( %{$href} ) ) {
|
||||||
|
# $k is '@aa', $v is a href
|
||||||
|
for my $k2 ( keys %{$v} ) {
|
||||||
|
# $k2 is bb, then cc
|
||||||
|
push @{ $ret{$k2} }, $k;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return \%ret;
|
||||||
|
# %groups = ( 'bb' => [ '@bb', '@aa' ], 'cc' => [ '@bb', '@aa' ], 'dd' => [ '@bb' ]);
|
||||||
|
}
|
||||||
|
|
||||||
|
1;
|
||||||
|
|
194
src/lib/Gitolite/Conf/Sugar.pm
Normal file
194
src/lib/Gitolite/Conf/Sugar.pm
Normal file
|
@ -0,0 +1,194 @@
|
||||||
|
# and now for something completely different...
|
||||||
|
|
||||||
|
package SugarBox;
|
||||||
|
|
||||||
|
sub run_sugar_script {
|
||||||
|
my ( $ss, $lref ) = @_;
|
||||||
|
do $ss if -r $ss;
|
||||||
|
$lref = sugar_script($lref);
|
||||||
|
return $lref;
|
||||||
|
}
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
package Gitolite::Conf::Sugar;
|
||||||
|
|
||||||
|
# syntactic sugar for the conf file, including site-local macros
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
@EXPORT = qw(
|
||||||
|
sugar
|
||||||
|
);
|
||||||
|
|
||||||
|
use Exporter 'import';
|
||||||
|
|
||||||
|
use Gitolite::Rc;
|
||||||
|
use Gitolite::Common;
|
||||||
|
use Gitolite::Conf::Explode;
|
||||||
|
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
sub sugar {
|
||||||
|
# gets a filename, returns a listref
|
||||||
|
|
||||||
|
my @lines = ();
|
||||||
|
explode( shift, 'master', \@lines );
|
||||||
|
|
||||||
|
my $lines;
|
||||||
|
$lines = \@lines;
|
||||||
|
|
||||||
|
# run through the sugar stack one by one
|
||||||
|
|
||||||
|
# first, user supplied sugar:
|
||||||
|
if ( exists $rc{SYNTACTIC_SUGAR} ) {
|
||||||
|
if ( ref( $rc{SYNTACTIC_SUGAR} ) ne 'ARRAY' ) {
|
||||||
|
_warn "bad syntax for specifying sugar scripts; see docs";
|
||||||
|
} else {
|
||||||
|
for my $s ( @{ $rc{SYNTACTIC_SUGAR} } ) {
|
||||||
|
|
||||||
|
# perl-ism; apart from keeping the full path separate from the
|
||||||
|
# simple name, this also protects %rc from change by implicit
|
||||||
|
# aliasing, which would happen if you touched $s itself
|
||||||
|
my $sfp = _which("syntactic-sugar/$s", 'r');
|
||||||
|
|
||||||
|
_warn("skipped sugar script '$s'"), next if not -r $sfp;
|
||||||
|
$lines = SugarBox::run_sugar_script( $sfp, $lines );
|
||||||
|
$lines = [ grep /\S/, map { cleanup_conf_line($_) } @$lines ];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# then our stuff:
|
||||||
|
|
||||||
|
$lines = rw_cdm($lines);
|
||||||
|
$lines = option($lines); # must come after rw_cdm
|
||||||
|
$lines = owner_desc($lines);
|
||||||
|
$lines = name_vref($lines);
|
||||||
|
$lines = role_names($lines);
|
||||||
|
|
||||||
|
return $lines;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub rw_cdm {
|
||||||
|
my $lines = shift;
|
||||||
|
my @ret;
|
||||||
|
|
||||||
|
# repo foo <...> RWC = ...
|
||||||
|
# -> option CREATE_IS_C = 1
|
||||||
|
# (and similarly DELETE_IS_D and MERGE_CHECK)
|
||||||
|
# but only once per repo of course
|
||||||
|
|
||||||
|
my %seen = ();
|
||||||
|
for my $line (@$lines) {
|
||||||
|
push @ret, $line;
|
||||||
|
if ( $line =~ /^repo / ) {
|
||||||
|
%seen = ();
|
||||||
|
} elsif ( $line =~ /^(-|C|R|RW\+?(?:C?D?|D?C?)M?) (.* )?= (.+)/ ) {
|
||||||
|
my $perms = $1;
|
||||||
|
push @ret, "option DELETE_IS_D = 1" if $perms =~ /D/ and not $seen{D}++;
|
||||||
|
push @ret, "option CREATE_IS_C = 1" if $perms =~ /RW.*C/ and not $seen{C}++;
|
||||||
|
push @ret, "option MERGE_CHECK = 1" if $perms =~ /M/ and not $seen{M}++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return \@ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub option {
|
||||||
|
my $lines = shift;
|
||||||
|
my @ret;
|
||||||
|
|
||||||
|
# option foo = bar
|
||||||
|
# -> config gitolite-options.foo = bar
|
||||||
|
|
||||||
|
for my $line (@$lines) {
|
||||||
|
if ( $line =~ /^option (\S+) = (\S.*)/ ) {
|
||||||
|
push @ret, "config gitolite-options.$1 = $2";
|
||||||
|
} else {
|
||||||
|
push @ret, $line;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return \@ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub owner_desc {
|
||||||
|
my $lines = shift;
|
||||||
|
my @ret;
|
||||||
|
|
||||||
|
# owner = "owner name"
|
||||||
|
# -> config gitweb.owner = owner name
|
||||||
|
# desc = "some long description"
|
||||||
|
# -> config gitweb.description = some long description
|
||||||
|
# category = "whatever..."
|
||||||
|
# -> config gitweb.category = whatever...
|
||||||
|
|
||||||
|
# older formats:
|
||||||
|
# repo = "some long description"
|
||||||
|
# repo "owner name" = "some long description"
|
||||||
|
# -> config gitweb.owner = owner name
|
||||||
|
# -> config gitweb.description = some long description
|
||||||
|
|
||||||
|
for my $line (@$lines) {
|
||||||
|
if ( $line =~ /^desc = (\S.*)/ ) {
|
||||||
|
push @ret, "config gitweb.description = $1";
|
||||||
|
} elsif ( $line =~ /^owner = (\S.*)/ ) {
|
||||||
|
push @ret, "config gitweb.owner = $1";
|
||||||
|
} elsif ( $line =~ /^category = (\S.*)/ ) {
|
||||||
|
push @ret, "config gitweb.category = $1";
|
||||||
|
} elsif ( $line =~ /^(\S+)(?: "(.*?)")? = "(.*)"$/ ) {
|
||||||
|
my ( $repo, $owner, $desc ) = ( $1, $2, $3 );
|
||||||
|
push @ret, "repo $repo";
|
||||||
|
push @ret, "config gitweb.description = $desc";
|
||||||
|
push @ret, "config gitweb.owner = $owner" if $owner;
|
||||||
|
} else {
|
||||||
|
push @ret, $line;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return \@ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub name_vref {
|
||||||
|
my $lines = shift;
|
||||||
|
my @ret;
|
||||||
|
|
||||||
|
# <perm> NAME/foo = <user>
|
||||||
|
# -> <perm> VREF/NAME/foo = <user>
|
||||||
|
|
||||||
|
for my $line (@$lines) {
|
||||||
|
if ( $line =~ /^(-|R\S+) \S.* = \S.*/ ) {
|
||||||
|
$line =~ s( NAME/)( VREF/NAME/)g;
|
||||||
|
}
|
||||||
|
push @ret, $line;
|
||||||
|
}
|
||||||
|
return \@ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub role_names {
|
||||||
|
my $lines = shift;
|
||||||
|
my @ret;
|
||||||
|
|
||||||
|
# <perm> [<ref>] = <user list containing CREATOR|READERS|WRITERS>
|
||||||
|
# -> same but with "@" prepended to rolenames
|
||||||
|
|
||||||
|
for my $line (@$lines) {
|
||||||
|
if ( $line =~ /^(-|C|R|RW\+?(?:C?D?|D?C?)M?) (.* )?= (.+)/ ) {
|
||||||
|
my ( $p, $r ) = ( $1, $2 );
|
||||||
|
my $u = '';
|
||||||
|
for ( split ' ', $3 ) {
|
||||||
|
$_ = "\@$_" if $_ eq 'CREATOR' or $rc{ROLES}{$_};
|
||||||
|
$u .= " $_";
|
||||||
|
}
|
||||||
|
$r ||= '';
|
||||||
|
# mind the spaces (or play safe and run cleanup_conf_line again)
|
||||||
|
push @ret, cleanup_conf_line("$p $r = $u");
|
||||||
|
} else {
|
||||||
|
push @ret, $line;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return \@ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
1;
|
||||||
|
|
173
src/lib/Gitolite/Easy.pm
Normal file
173
src/lib/Gitolite/Easy.pm
Normal file
|
@ -0,0 +1,173 @@
|
||||||
|
package Gitolite::Easy;
|
||||||
|
|
||||||
|
# easy access to gitolite from external perl programs
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
# most/all functions in this module test $ENV{GL_USER}'s rights and
|
||||||
|
# permissions so it needs to be set.
|
||||||
|
|
||||||
|
# "use"-ing this module
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
# Using this module from within a gitolite trigger or command is easy; you
|
||||||
|
# just need 'use lib $ENV{GL_LIBDIR};' before the 'use Gitolite::Easy;'.
|
||||||
|
#
|
||||||
|
# Using it from something completely outside gitolite requires a bit more
|
||||||
|
# work. First, run 'gitolite query-rc -a' to find the correct values for
|
||||||
|
# GL_BINDIR and GL_LIBDIR in your installation. Then use this code in your
|
||||||
|
# external program, using the paths you just found:
|
||||||
|
#
|
||||||
|
# BEGIN {
|
||||||
|
# $ENV{GL_BINDIR} = "/full/path/to/gitolite/src";
|
||||||
|
# $ENV{GL_LIBDIR} = "/full/path/to/gitolite/src/lib";
|
||||||
|
# }
|
||||||
|
# use lib $ENV{GL_LIBDIR};
|
||||||
|
# use Gitolite::Easy;
|
||||||
|
|
||||||
|
# API documentation
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
# documentation for each function is at the top of the function.
|
||||||
|
# Documentation is NOT in pod format; just read the source with a nice syntax
|
||||||
|
# coloring text editor and you'll be happy enough. (I do not like POD; please
|
||||||
|
# don't send me patches for this aspect of the module).
|
||||||
|
|
||||||
|
#<<<
|
||||||
|
@EXPORT = qw(
|
||||||
|
is_admin
|
||||||
|
is_super_admin
|
||||||
|
in_group
|
||||||
|
|
||||||
|
owns
|
||||||
|
can_read
|
||||||
|
can_write
|
||||||
|
|
||||||
|
config
|
||||||
|
|
||||||
|
%rc
|
||||||
|
say
|
||||||
|
say2
|
||||||
|
_die
|
||||||
|
_warn
|
||||||
|
_print
|
||||||
|
usage
|
||||||
|
);
|
||||||
|
#>>>
|
||||||
|
use Exporter 'import';
|
||||||
|
|
||||||
|
use Gitolite::Rc;
|
||||||
|
use Gitolite::Common;
|
||||||
|
use Gitolite::Conf::Load;
|
||||||
|
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
my $user;
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
# is_admin()
|
||||||
|
|
||||||
|
# return true if $ENV{GL_USER} is set and has W perms to the admin repo
|
||||||
|
|
||||||
|
# shell equivalent
|
||||||
|
# if gitolite access -q gitolite-admin $GL_USER W; then ...
|
||||||
|
|
||||||
|
sub is_admin {
|
||||||
|
valid_user();
|
||||||
|
return not( access( 'gitolite-admin', $user, 'W', 'any' ) =~ /DENIED/ );
|
||||||
|
}
|
||||||
|
|
||||||
|
# is_super_admin()
|
||||||
|
|
||||||
|
# (useful only if you are using delegation)
|
||||||
|
|
||||||
|
# return true if $ENV{GL_USER} is set and has W perms to any file in the admin
|
||||||
|
# repo
|
||||||
|
|
||||||
|
# shell equivalent
|
||||||
|
# if gitolite access -q gitolite-admin $GL_USER W VREF/NAME/; then ...
|
||||||
|
sub is_super_admin {
|
||||||
|
valid_user();
|
||||||
|
return not( access( 'gitolite-admin', $user, 'W', 'VREF/NAME/' ) =~ /DENIED/ );
|
||||||
|
}
|
||||||
|
|
||||||
|
# in_group()
|
||||||
|
|
||||||
|
# return true if $ENV{GL_USER} is set and is in the given group
|
||||||
|
|
||||||
|
# shell equivalent
|
||||||
|
# if gitolite list-memberships $GL_USER | grep -x $GROUPNAME >/dev/null; then ...
|
||||||
|
sub in_group {
|
||||||
|
valid_user();
|
||||||
|
my $g = shift;
|
||||||
|
$g =~ s/^\@?/@/;
|
||||||
|
|
||||||
|
return grep { $_ eq $g } @{ Gitolite::Conf::Load::list_memberships($user) };
|
||||||
|
}
|
||||||
|
|
||||||
|
# owns()
|
||||||
|
|
||||||
|
# return true if $ENV{GL_USER} is set and is the creator of the given repo
|
||||||
|
|
||||||
|
# shell equivalent
|
||||||
|
# if gitolite creator $REPONAME $GL_USER; then ...
|
||||||
|
sub owns {
|
||||||
|
valid_user();
|
||||||
|
my $r = shift;
|
||||||
|
|
||||||
|
# prevent unnecessary disclosure of repo existence info
|
||||||
|
return 0 if repo_missing($r);
|
||||||
|
|
||||||
|
return ( creator($r) eq $user );
|
||||||
|
}
|
||||||
|
|
||||||
|
# can_read()
|
||||||
|
# return true if $ENV{GL_USER} is set and can read the given repo
|
||||||
|
|
||||||
|
# shell equivalent
|
||||||
|
# if gitolite access -q $REPONAME $GL_USER R; then ...
|
||||||
|
sub can_read {
|
||||||
|
valid_user();
|
||||||
|
my $r = shift;
|
||||||
|
return not( access( $r, $user, 'R', 'any' ) =~ /DENIED/ );
|
||||||
|
}
|
||||||
|
|
||||||
|
# can_write()
|
||||||
|
# return true if $ENV{GL_USER} is set and can write to the given repo.
|
||||||
|
# Optional second argument can be '+' to check that instead of 'W'. Optional
|
||||||
|
# third argument can be a full ref name instead of 'any'.
|
||||||
|
|
||||||
|
# shell equivalent
|
||||||
|
# if gitolite access -q $REPONAME $GL_USER W; then ...
|
||||||
|
sub can_write {
|
||||||
|
valid_user();
|
||||||
|
my ($r, $aa, $ref) = @_;
|
||||||
|
$aa ||= 'W';
|
||||||
|
$ref ||= 'any';
|
||||||
|
return not( access( $r, $user, $aa, $ref ) =~ /DENIED/ );
|
||||||
|
}
|
||||||
|
|
||||||
|
# config()
|
||||||
|
# given a repo and a key, return a hash containing all the git config
|
||||||
|
# variables for that repo where the section+key match the regex. If none are
|
||||||
|
# found, return an empty hash. If you don't want it as a regex, use \Q
|
||||||
|
# appropriately
|
||||||
|
|
||||||
|
# shell equivalent
|
||||||
|
# foo=$(gitolite git-config -r $REPONAME foo\\.bar)
|
||||||
|
sub config {
|
||||||
|
my $repo = shift;
|
||||||
|
my $key = shift;
|
||||||
|
|
||||||
|
return () if repo_missing($repo);
|
||||||
|
|
||||||
|
my $ret = git_config( $repo, $key );
|
||||||
|
return %$ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
sub valid_user {
|
||||||
|
_die "GL_USER not set" unless exists $ENV{GL_USER};
|
||||||
|
$user = $ENV{GL_USER};
|
||||||
|
}
|
||||||
|
|
||||||
|
1;
|
74
src/lib/Gitolite/Hooks/PostUpdate.pm
Normal file
74
src/lib/Gitolite/Hooks/PostUpdate.pm
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
package Gitolite::Hooks::PostUpdate;
|
||||||
|
|
||||||
|
# everything to do with the post-update hook
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
@EXPORT = qw(
|
||||||
|
post_update
|
||||||
|
post_update_hook
|
||||||
|
);
|
||||||
|
|
||||||
|
use Exporter 'import';
|
||||||
|
|
||||||
|
use Gitolite::Rc;
|
||||||
|
use Gitolite::Common;
|
||||||
|
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
sub post_update {
|
||||||
|
trace( 1, 'post-up', @ARGV );
|
||||||
|
# this is the *real* post_update hook for gitolite
|
||||||
|
|
||||||
|
tsh_try("git ls-tree --name-only master");
|
||||||
|
_die "no files/dirs called 'hooks' or 'logs' are allowed" if tsh_text() =~ /^(hooks|logs)$/m;
|
||||||
|
|
||||||
|
my $hooks_changed = 0;
|
||||||
|
{
|
||||||
|
local $ENV{GIT_WORK_TREE} = $rc{GL_ADMIN_BASE};
|
||||||
|
|
||||||
|
tsh_try("git diff --name-only master");
|
||||||
|
$hooks_changed++ if tsh_text() =~ m(/hooks/common/);
|
||||||
|
# the leading slash ensure that this hooks/common directory is below
|
||||||
|
# some top level directory, not *at* the top. That's LOCAL_CODE, and
|
||||||
|
# it's actual name could be anything but it doesn't matter to us.
|
||||||
|
|
||||||
|
tsh_try("git checkout -f --quiet master");
|
||||||
|
}
|
||||||
|
_system("gitolite compile");
|
||||||
|
_system("gitolite setup --hooks-only") if $hooks_changed;
|
||||||
|
_system("gitolite trigger POST_COMPILE");
|
||||||
|
|
||||||
|
exit 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
my $text = '';
|
||||||
|
|
||||||
|
sub post_update_hook {
|
||||||
|
if ( not $text ) {
|
||||||
|
local $/ = undef;
|
||||||
|
$text = <DATA>;
|
||||||
|
}
|
||||||
|
return $text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
1;
|
||||||
|
|
||||||
|
__DATA__
|
||||||
|
#!/usr/bin/perl
|
||||||
|
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
use lib $ENV{GL_LIBDIR};
|
||||||
|
use Gitolite::Hooks::PostUpdate;
|
||||||
|
|
||||||
|
# gitolite post-update hook (only for the admin repo)
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
post_update(); # is not expected to return
|
||||||
|
exit 1; # so if it does, something is wrong
|
169
src/lib/Gitolite/Hooks/Update.pm
Normal file
169
src/lib/Gitolite/Hooks/Update.pm
Normal file
|
@ -0,0 +1,169 @@
|
||||||
|
package Gitolite::Hooks::Update;
|
||||||
|
|
||||||
|
# everything to do with the update hook
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
@EXPORT = qw(
|
||||||
|
update
|
||||||
|
update_hook
|
||||||
|
);
|
||||||
|
|
||||||
|
use Exporter 'import';
|
||||||
|
|
||||||
|
use Gitolite::Rc;
|
||||||
|
use Gitolite::Common;
|
||||||
|
use Gitolite::Conf::Load;
|
||||||
|
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
sub update {
|
||||||
|
# this is the *real* update hook for gitolite
|
||||||
|
|
||||||
|
bypass() if $ENV{GL_BYPASS_ACCESS_CHECKS};
|
||||||
|
|
||||||
|
my ( $ref, $oldsha, $newsha, $oldtree, $newtree, $aa ) = args(@ARGV);
|
||||||
|
|
||||||
|
trace( 1, 'update', $ENV{GL_REPO}, $ENV{GL_USER}, $aa, @ARGV );
|
||||||
|
|
||||||
|
my $ret = access( $ENV{GL_REPO}, $ENV{GL_USER}, $aa, $ref );
|
||||||
|
trigger( 'ACCESS_2', $ENV{GL_REPO}, $ENV{GL_USER}, $aa, $ref, $ret, $oldsha, $newsha );
|
||||||
|
_die $ret if $ret =~ /DENIED/;
|
||||||
|
|
||||||
|
check_vrefs( $ref, $oldsha, $newsha, $oldtree, $newtree, $aa );
|
||||||
|
|
||||||
|
trace( 1, "-> $ret" );
|
||||||
|
gl_log( 'update', $ENV{GL_REPO}, $ENV{GL_USER}, $aa, @ARGV );
|
||||||
|
exit 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub bypass {
|
||||||
|
require Cwd;
|
||||||
|
Cwd->import;
|
||||||
|
gl_log( 'update', getcwd(), '(' . ( $ENV{USER} || '?' ) . ')', 'bypass', @ARGV );
|
||||||
|
exit 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub check_vrefs {
|
||||||
|
my ( $ref, $oldsha, $newsha, $oldtree, $newtree, $aa ) = @_;
|
||||||
|
my $name_seen = 0;
|
||||||
|
my $n_vrefs = 0;
|
||||||
|
for my $vref ( vrefs( $ENV{GL_REPO}, $ENV{GL_USER} ) ) {
|
||||||
|
$n_vrefs++;
|
||||||
|
if ( $vref =~ m(^VREF/NAME/) ) {
|
||||||
|
# this one is special; we process it right here, and only once
|
||||||
|
next if $name_seen++;
|
||||||
|
|
||||||
|
for my $ref ( map { chomp; s(^)(VREF/NAME/); $_; } `git diff --name-only $oldtree $newtree` ) {
|
||||||
|
check_vref( $aa, $ref );
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
my ( $dummy, $pgm, @args ) = split '/', $vref;
|
||||||
|
$pgm = _which("VREF/$pgm", 'x');
|
||||||
|
$pgm or _die "'$vref': helper program missing or unexecutable";
|
||||||
|
|
||||||
|
open( my $fh, "-|", $pgm, @_, $vref, @args ) or _die "'$vref': can't spawn helper program: $!";
|
||||||
|
while (<$fh>) {
|
||||||
|
# print non-vref lines and skip processing (for example,
|
||||||
|
# normal STDOUT by a normal update hook)
|
||||||
|
unless (m(^VREF/)) {
|
||||||
|
print;
|
||||||
|
next;
|
||||||
|
}
|
||||||
|
my ( $ref, $deny_message ) = split( ' ', $_, 2 );
|
||||||
|
check_vref( $aa, $ref, $deny_message );
|
||||||
|
}
|
||||||
|
close($fh) or _die $!
|
||||||
|
? "Error closing sort pipe: $!"
|
||||||
|
: "$vref: helper program exit status $?";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $n_vrefs;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub check_vref {
|
||||||
|
my ( $aa, $ref, $deny_message ) = @_;
|
||||||
|
|
||||||
|
my $ret = access( $ENV{GL_REPO}, $ENV{GL_USER}, $aa, $ref );
|
||||||
|
trace( 2, "access($ENV{GL_REPO}, $ENV{GL_USER}, $aa, $ref)", "-> $ret" );
|
||||||
|
trigger( 'ACCESS_2', $ENV{GL_REPO}, $ENV{GL_USER}, $aa, $ref, $ret );
|
||||||
|
_die "$ret" . ( $deny_message ? "\n$deny_message" : '' )
|
||||||
|
if $ret =~ /DENIED/ and $ret !~ /by fallthru/;
|
||||||
|
trace( 2, "remember, fallthru is success here!" ) if $ret =~ /by fallthru/;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
my $text = '';
|
||||||
|
|
||||||
|
sub update_hook {
|
||||||
|
if ( not $text ) {
|
||||||
|
local $/ = undef;
|
||||||
|
$text = <DATA>;
|
||||||
|
}
|
||||||
|
return $text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
sub args {
|
||||||
|
my ( $ref, $oldsha, $newsha ) = @_;
|
||||||
|
my ( $oldtree, $newtree, $aa );
|
||||||
|
|
||||||
|
# this is special to git -- the hash of an empty tree
|
||||||
|
my $empty = '4b825dc642cb6eb9a060e54bf8d69288fbee4904';
|
||||||
|
$oldtree = $oldsha eq '0' x 40 ? $empty : $oldsha;
|
||||||
|
$newtree = $newsha eq '0' x 40 ? $empty : $newsha;
|
||||||
|
|
||||||
|
my $merge_base = '0' x 40;
|
||||||
|
# for branch create or delete, merge_base stays at '0'x40
|
||||||
|
chomp( $merge_base = `git merge-base $oldsha $newsha` )
|
||||||
|
unless $oldsha eq '0' x 40
|
||||||
|
or $newsha eq '0' x 40;
|
||||||
|
|
||||||
|
$aa = 'W';
|
||||||
|
# tag rewrite
|
||||||
|
$aa = '+' if $ref =~ m(refs/tags/) and $oldsha ne ( '0' x 40 );
|
||||||
|
# non-ff push to ref (including ref delete)
|
||||||
|
$aa = '+' if $oldsha ne $merge_base;
|
||||||
|
|
||||||
|
$aa = 'D' if ( option( $ENV{GL_REPO}, 'DELETE_IS_D' ) ) and $newsha eq '0' x 40;
|
||||||
|
$aa = 'C' if ( option( $ENV{GL_REPO}, 'CREATE_IS_C' ) ) and $oldsha eq '0' x 40;
|
||||||
|
|
||||||
|
# and now "M" commits. All the other accesses (W, +, C, D) were mutually
|
||||||
|
# exclusive in some sense. Sure a W could be a C or a + could be a D but
|
||||||
|
# that's by design. A merge commit, however, could still be any of the
|
||||||
|
# others (except a "D").
|
||||||
|
|
||||||
|
# so we have to *append* 'M' to $aa (if the repo has MERGE_CHECK in
|
||||||
|
# effect and this push contains a merge inside)
|
||||||
|
|
||||||
|
if ( option( $ENV{GL_REPO}, 'MERGE_CHECK' ) ) {
|
||||||
|
if ( $oldsha eq '0' x 40 or $newsha eq '0' x 40 ) {
|
||||||
|
_warn "ref create/delete ignored for purposes of merge-check\n";
|
||||||
|
} else {
|
||||||
|
$aa .= 'M' if `git rev-list -n 1 --merges $oldsha..$newsha` =~ /./;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ( $ref, $oldsha, $newsha, $oldtree, $newtree, $aa );
|
||||||
|
}
|
||||||
|
|
||||||
|
1;
|
||||||
|
|
||||||
|
__DATA__
|
||||||
|
#!/usr/bin/perl
|
||||||
|
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
use lib $ENV{GL_LIBDIR};
|
||||||
|
use Gitolite::Hooks::Update;
|
||||||
|
|
||||||
|
# gitolite update hook
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
update(); # is not expected to return
|
||||||
|
exit 1; # so if it does, something is wrong
|
454
src/lib/Gitolite/Rc.pm
Normal file
454
src/lib/Gitolite/Rc.pm
Normal file
|
@ -0,0 +1,454 @@
|
||||||
|
package Gitolite::Rc;
|
||||||
|
|
||||||
|
# everything to do with 'rc'. Also defines some 'constants'
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
@EXPORT = qw(
|
||||||
|
%rc
|
||||||
|
glrc
|
||||||
|
query_rc
|
||||||
|
version
|
||||||
|
trigger
|
||||||
|
_which
|
||||||
|
|
||||||
|
$REMOTE_COMMAND_PATT
|
||||||
|
$REF_OR_FILENAME_PATT
|
||||||
|
$REPONAME_PATT
|
||||||
|
$REPOPATT_PATT
|
||||||
|
$USERNAME_PATT
|
||||||
|
$UNSAFE_PATT
|
||||||
|
);
|
||||||
|
|
||||||
|
use Exporter 'import';
|
||||||
|
use Getopt::Long;
|
||||||
|
|
||||||
|
use Gitolite::Common;
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
our %rc;
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
# pre-populate some important rc keys
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
$rc{GL_BINDIR} = $ENV{GL_BINDIR};
|
||||||
|
$rc{GL_LIBDIR} = $ENV{GL_LIBDIR};
|
||||||
|
|
||||||
|
# these keys could be overridden by the rc file later
|
||||||
|
$rc{GL_REPO_BASE} = "$ENV{HOME}/repositories";
|
||||||
|
$rc{GL_ADMIN_BASE} = "$ENV{HOME}/.gitolite";
|
||||||
|
$rc{LOG_TEMPLATE} = "$ENV{HOME}/.gitolite/logs/gitolite-%y-%m.log";
|
||||||
|
|
||||||
|
# variables that should probably never be changed but someone will want to, I'll bet...
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
#<<<
|
||||||
|
$REMOTE_COMMAND_PATT = qr(^[-0-9a-zA-Z._\@/+ :,\%=]*$);
|
||||||
|
$REF_OR_FILENAME_PATT = qr(^[0-9a-zA-Z][-0-9a-zA-Z._\@/+ :,]*$);
|
||||||
|
$REPONAME_PATT = qr(^\@?[0-9a-zA-Z][-0-9a-zA-Z._\@/+]*$);
|
||||||
|
$REPOPATT_PATT = qr(^\@?[[0-9a-zA-Z][-0-9a-zA-Z._\@/+\\^$|()[\]*?{},]*$);
|
||||||
|
$USERNAME_PATT = qr(^\@?[0-9a-zA-Z][-0-9a-zA-Z._\@+]*$);
|
||||||
|
|
||||||
|
$UNSAFE_PATT = qr([`~#\$\&()|;<>]);
|
||||||
|
#>>>
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
# find the rc file and 'do' it
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
my $current_data_version = "3.2";
|
||||||
|
|
||||||
|
my $rc = glrc('filename');
|
||||||
|
if (-r $rc and -s $rc) {
|
||||||
|
do $rc or die $@;
|
||||||
|
}
|
||||||
|
if ( defined($GL_ADMINDIR) ) {
|
||||||
|
say2 "";
|
||||||
|
say2 "FATAL: '$rc' seems to be for older gitolite; please see doc/g2migr.mkd\n" . "(online at http://sitaramc.github.com/gitolite/g2migr.html)";
|
||||||
|
|
||||||
|
exit 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
# let values specified in rc file override our internal ones
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
@rc{ keys %RC } = values %RC;
|
||||||
|
|
||||||
|
# add internal triggers
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
# is the server/repo in a writable state (i.e., not down for maintenance etc)
|
||||||
|
unshift @{ $rc{ACCESS_1} }, 'Writable::access_1';
|
||||||
|
|
||||||
|
# (testing only) override the rc file silently
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
# use an env var that is highly unlikely to appear in real life :)
|
||||||
|
do $ENV{G3T_RC} if exists $ENV{G3T_RC} and -r $ENV{G3T_RC};
|
||||||
|
|
||||||
|
# setup some perl/rc/env vars
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
unshift @INC, "$rc{LOCAL_CODE}/lib" if $rc{LOCAL_CODE};
|
||||||
|
|
||||||
|
$ENV{PATH} = "$ENV{GL_BINDIR}:$ENV{PATH}" unless $ENV{PATH} =~ /^$ENV{GL_BINDIR}:/;
|
||||||
|
|
||||||
|
{
|
||||||
|
$rc{GL_TID} = $ENV{GL_TID} ||= $$;
|
||||||
|
# TID: loosely, transaction ID. The first PID at the entry point passes
|
||||||
|
# it down to all its children so you can track each access, across all the
|
||||||
|
# various commands it spawns and actions it generates.
|
||||||
|
|
||||||
|
$rc{GL_LOGFILE} = $ENV{GL_LOGFILE} ||= gen_lfn( $rc{LOG_TEMPLATE} );
|
||||||
|
}
|
||||||
|
|
||||||
|
# these two are meant to help externally written commands (see
|
||||||
|
# src/commands/writable for an example)
|
||||||
|
$ENV{GL_REPO_BASE} = $rc{GL_REPO_BASE};
|
||||||
|
$ENV{GL_ADMIN_BASE} = $rc{GL_ADMIN_BASE};
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
my $glrc_default_text = '';
|
||||||
|
{
|
||||||
|
local $/ = undef;
|
||||||
|
$glrc_default_text = <DATA>;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub glrc {
|
||||||
|
my $cmd = shift;
|
||||||
|
if ( $cmd eq 'default-filename' ) {
|
||||||
|
return "$ENV{HOME}/.gitolite.rc";
|
||||||
|
} elsif ( $cmd eq 'default-text' ) {
|
||||||
|
return $glrc_default_text if $glrc_default_text;
|
||||||
|
_die "rc file default text not set; this should not happen!";
|
||||||
|
} elsif ( $cmd eq 'filename' ) {
|
||||||
|
# where is the rc file?
|
||||||
|
|
||||||
|
# search $HOME first
|
||||||
|
return "$ENV{HOME}/.gitolite.rc" if -f "$ENV{HOME}/.gitolite.rc";
|
||||||
|
|
||||||
|
return '';
|
||||||
|
} elsif ( $cmd eq 'current-data-version' ) {
|
||||||
|
return $current_data_version;
|
||||||
|
} else {
|
||||||
|
_die "unknown argument to glrc: '$cmd'";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# exported functions
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
my $all = 0;
|
||||||
|
my $nonl = 0;
|
||||||
|
my $quiet = 0;
|
||||||
|
|
||||||
|
sub query_rc {
|
||||||
|
|
||||||
|
my @vars = args();
|
||||||
|
|
||||||
|
no strict 'refs';
|
||||||
|
|
||||||
|
if ($all) {
|
||||||
|
for my $e ( sort keys %rc ) {
|
||||||
|
print "$e=" . ( defined( $rc{$e} ) ? $rc{$e} : 'undef' ) . "\n";
|
||||||
|
}
|
||||||
|
exit 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
my $cv = \%rc; # current "value"
|
||||||
|
while (@vars) {
|
||||||
|
my $v = shift @vars;
|
||||||
|
|
||||||
|
# dig into the rc hash, using each var as a component
|
||||||
|
if (not ref($cv)) {
|
||||||
|
_warn "unused arguments...";
|
||||||
|
last;
|
||||||
|
} elsif (ref($cv) eq 'HASH') {
|
||||||
|
$cv = $cv->{$v} || '';
|
||||||
|
} elsif (ref($cv) eq 'ARRAY') {
|
||||||
|
$cv = $cv->[$v] || '';
|
||||||
|
} else {
|
||||||
|
_die "dont know what to do with " . ref($cv) . " item in the rc file";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# we've run out of arguments so $cv is what we have. If we're supposed to
|
||||||
|
# be quiet, we don't have to print anything so let's get that done first:
|
||||||
|
exit ( $cv ? 0 : 1 ) if $quiet; # shell truth
|
||||||
|
|
||||||
|
# print values (notice we ignore the '-n' option if it's a ref)
|
||||||
|
if (ref($cv) eq 'HASH') {
|
||||||
|
print join("\n", sort keys %$cv), "\n" if %$cv;
|
||||||
|
} elsif (ref($cv) eq 'ARRAY') {
|
||||||
|
print join("\n", @$cv), "\n" if @$cv;
|
||||||
|
} else {
|
||||||
|
print $cv . ( $nonl ? '' : "\n" ) if $cv;
|
||||||
|
}
|
||||||
|
exit ( $cv ? 0 : 1 ); # shell truth
|
||||||
|
}
|
||||||
|
|
||||||
|
sub version {
|
||||||
|
my $version = '';
|
||||||
|
$version = '(unknown)';
|
||||||
|
for ("$ENV{GL_BINDIR}/VERSION") {
|
||||||
|
$version = slurp($_) if -r $_;
|
||||||
|
}
|
||||||
|
chomp($version);
|
||||||
|
return $version;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub trigger {
|
||||||
|
my $rc_section = shift;
|
||||||
|
|
||||||
|
if ( exists $rc{$rc_section} ) {
|
||||||
|
if ( ref( $rc{$rc_section} ) ne 'ARRAY' ) {
|
||||||
|
_die "'$rc_section' section in rc file is not a perl list";
|
||||||
|
} else {
|
||||||
|
for my $s ( @{ $rc{$rc_section} } ) {
|
||||||
|
my ( $pgm, @args ) = split ' ', $s;
|
||||||
|
|
||||||
|
if ( my ( $module, $sub ) = ( $pgm =~ /^(.*)::(\w+)$/ ) ) {
|
||||||
|
|
||||||
|
require Gitolite::Triggers;
|
||||||
|
trace( 1, 'trigger', $module, $sub, @args, $rc_section, @_ );
|
||||||
|
Gitolite::Triggers::run( $module, $sub, @args, $rc_section, @_ );
|
||||||
|
|
||||||
|
} else {
|
||||||
|
$pgm = _which("triggers/$pgm", 'x');
|
||||||
|
|
||||||
|
_warn("skipped command '$s'"), next if not $pgm;
|
||||||
|
trace( 2, "command: $s" );
|
||||||
|
_system( $pgm, @args, $rc_section, @_ ); # they better all return with 0 exit codes!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
trace( 2, "'$rc_section' not found in rc" );
|
||||||
|
}
|
||||||
|
|
||||||
|
sub _which {
|
||||||
|
# looks for a file in LOCAL_CODE or GL_BINDIR. Returns whichever exists
|
||||||
|
# (LOCAL_CODE preferred if defined) or 0 if not found.
|
||||||
|
my $file = shift;
|
||||||
|
my $mode = shift; # could be 'x' or 'r'
|
||||||
|
|
||||||
|
my @files = ("$rc{GL_BINDIR}/$file");
|
||||||
|
unshift @files, ("$rc{LOCAL_CODE}/$file") if $rc{LOCAL_CODE};
|
||||||
|
|
||||||
|
for my $f ( @files ) {
|
||||||
|
return $f if -x $f;
|
||||||
|
return $f if -r $f and $mode eq 'r';
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
=for args
|
||||||
|
Usage: gitolite query-rc -a
|
||||||
|
gitolite query-rc [-n] [-q] rc-variable
|
||||||
|
|
||||||
|
-a print all variables and values (first level only)
|
||||||
|
-n do not append a newline if variable is scalar
|
||||||
|
-q exit code only (shell truth; 0 is success)
|
||||||
|
|
||||||
|
Query the rc hash. Second and subsequent arguments dig deeper into the hash.
|
||||||
|
The examples are for the default configuration; yours may be different.
|
||||||
|
|
||||||
|
Single values:
|
||||||
|
gitolite query-rc GL_ADMIN_BASE # prints "/home/git/.gitolite" or similar
|
||||||
|
gitolite query-rc UMASK # prints "63" (that's 0077 in decimal!)
|
||||||
|
|
||||||
|
Hashes:
|
||||||
|
gitolite query-rc COMMANDS
|
||||||
|
# prints "desc", "help", "info", "perms", "writable", one per line
|
||||||
|
gitolite query-rc COMMANDS help # prints 1
|
||||||
|
gitolite query-rc -q COMMANDS help # prints nothing; exit code is 0
|
||||||
|
gitolite query-rc COMMANDS fork # prints nothing; exit code is 1
|
||||||
|
|
||||||
|
Arrays (somewhat less useful):
|
||||||
|
gitolite query-rc POST_GIT # prints nothing; exit code is 0
|
||||||
|
gitolite query-rc POST_COMPILE # prints 4 lines
|
||||||
|
gitolite query-rc POST_COMPILE 0 # prints the first of those 4 lines
|
||||||
|
|
||||||
|
Explore:
|
||||||
|
gitolite query-rc -a
|
||||||
|
# prints all first level variables and values, one per line. Any that are
|
||||||
|
# listed as HASH or ARRAY can be explored further in subsequent commands.
|
||||||
|
=cut
|
||||||
|
|
||||||
|
sub args {
|
||||||
|
my $help = 0;
|
||||||
|
|
||||||
|
GetOptions(
|
||||||
|
'all|a' => \$all,
|
||||||
|
'nonl|n' => \$nonl,
|
||||||
|
'quiet|q' => \$quiet,
|
||||||
|
'help|h' => \$help,
|
||||||
|
) or usage();
|
||||||
|
|
||||||
|
usage("'-a' cannot be combined with other arguments or options") if $all and ( @ARGV or $nonl or $quiet );
|
||||||
|
usage() if not $all and not @ARGV or $help;
|
||||||
|
return @ARGV;
|
||||||
|
}
|
||||||
|
|
||||||
|
1;
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
__DATA__
|
||||||
|
# configuration variables for gitolite
|
||||||
|
|
||||||
|
# This file is in perl syntax. 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!
|
||||||
|
|
||||||
|
# (Tip: perl allows a comma after the last item in a list also!)
|
||||||
|
|
||||||
|
# HELP for commands (see COMMANDS list below) can be had by running the
|
||||||
|
# command with "-h" as the sole argument.
|
||||||
|
|
||||||
|
# HELP for all the other external programs (the syntactic sugar helpers and
|
||||||
|
# the various programs/functions in the 8 trigger lists), can be found in
|
||||||
|
# doc/non-core.mkd (http://sitaramc.github.com/gitolite/non-core.html) or in
|
||||||
|
# the corresponding source file itself.
|
||||||
|
|
||||||
|
%RC = (
|
||||||
|
# if you're using mirroring, you need a hostname. This is *one* simple
|
||||||
|
# word, not a full domain name. See documentation if in doubt
|
||||||
|
# HOSTNAME => 'darkstar',
|
||||||
|
UMASK => 0077,
|
||||||
|
|
||||||
|
# look in the "GIT-CONFIG" section in the README for what to do
|
||||||
|
GIT_CONFIG_KEYS => '',
|
||||||
|
|
||||||
|
# comment out if you don't need all the extra detail in the logfile
|
||||||
|
LOG_EXTRA => 1,
|
||||||
|
|
||||||
|
# settings used by external programs; uncomment and change as needed. You
|
||||||
|
# can add your own variables for use in your own external programs; take a
|
||||||
|
# look at the info and desc commands for perl and shell samples.
|
||||||
|
|
||||||
|
# used by the CpuTime trigger
|
||||||
|
# DISPLAY_CPU_TIME => 1,
|
||||||
|
# CPU_TIME_WARN_LIMIT => 0.1,
|
||||||
|
# used by the desc command
|
||||||
|
# WRITER_CAN_UPDATE_DESC => 1,
|
||||||
|
# used by the info command
|
||||||
|
# SITE_INFO => 'Please see http://blahblah/gitolite for more help',
|
||||||
|
|
||||||
|
# add more roles (like MANAGER, TESTER, ...) here.
|
||||||
|
# WARNING: if you make changes to this hash, you MUST run 'gitolite
|
||||||
|
# compile' afterward, and possibly also 'gitolite trigger POST_COMPILE'
|
||||||
|
ROLES =>
|
||||||
|
{
|
||||||
|
READERS => 1,
|
||||||
|
WRITERS => 1,
|
||||||
|
},
|
||||||
|
# uncomment (and change) this if you wish
|
||||||
|
# DEFAULT_ROLE_PERMS => 'READERS @all',
|
||||||
|
|
||||||
|
# comment out or uncomment as needed
|
||||||
|
# these are available to remote users
|
||||||
|
COMMANDS =>
|
||||||
|
{
|
||||||
|
'help' => 1,
|
||||||
|
'desc' => 1,
|
||||||
|
# 'fork' => 1,
|
||||||
|
'info' => 1,
|
||||||
|
# 'mirror' => 1,
|
||||||
|
'perms' => 1,
|
||||||
|
# 'sskm' => 1,
|
||||||
|
'writable' => 1,
|
||||||
|
# 'D' => 1,
|
||||||
|
},
|
||||||
|
|
||||||
|
# comment out or uncomment as needed
|
||||||
|
# these will run in sequence during the conf file parse
|
||||||
|
SYNTACTIC_SUGAR =>
|
||||||
|
[
|
||||||
|
# 'continuation-lines',
|
||||||
|
# 'keysubdirs-as-groups',
|
||||||
|
],
|
||||||
|
|
||||||
|
# comment out or uncomment as needed
|
||||||
|
# these will run in sequence to modify the input (arguments and environment)
|
||||||
|
INPUT =>
|
||||||
|
[
|
||||||
|
# 'CpuTime::input',
|
||||||
|
# 'Shell::input',
|
||||||
|
# 'Alias::input',
|
||||||
|
# 'Mirroring::input',
|
||||||
|
],
|
||||||
|
|
||||||
|
# comment out or uncomment as needed
|
||||||
|
# these will run in sequence just after the first access check is done
|
||||||
|
ACCESS_1 =>
|
||||||
|
[
|
||||||
|
],
|
||||||
|
|
||||||
|
# comment out or uncomment as needed
|
||||||
|
# these will run in sequence just before the actual git command is invoked
|
||||||
|
PRE_GIT =>
|
||||||
|
[
|
||||||
|
# 'renice 10',
|
||||||
|
# 'Mirroring::pre_git',
|
||||||
|
# 'partial-copy',
|
||||||
|
],
|
||||||
|
|
||||||
|
# comment out or uncomment as needed
|
||||||
|
# these will run in sequence just after the second access check is done
|
||||||
|
ACCESS_2 =>
|
||||||
|
[
|
||||||
|
],
|
||||||
|
|
||||||
|
# comment out or uncomment as needed
|
||||||
|
# these will run in sequence after the git command returns
|
||||||
|
POST_GIT =>
|
||||||
|
[
|
||||||
|
# 'Mirroring::post_git',
|
||||||
|
# 'CpuTime::post_git',
|
||||||
|
],
|
||||||
|
|
||||||
|
# comment out or uncomment as needed
|
||||||
|
# these will run in sequence before a new wild repo is created
|
||||||
|
PRE_CREATE =>
|
||||||
|
[
|
||||||
|
],
|
||||||
|
|
||||||
|
# comment out or uncomment as needed
|
||||||
|
# these will run in sequence after a new repo is created
|
||||||
|
POST_CREATE =>
|
||||||
|
[
|
||||||
|
'post-compile/update-git-configs',
|
||||||
|
'post-compile/update-gitweb-access-list',
|
||||||
|
'post-compile/update-git-daemon-access-list',
|
||||||
|
],
|
||||||
|
|
||||||
|
# comment out or uncomment as needed
|
||||||
|
# these will run in sequence after post-update
|
||||||
|
POST_COMPILE =>
|
||||||
|
[
|
||||||
|
'post-compile/ssh-authkeys',
|
||||||
|
'post-compile/update-git-configs',
|
||||||
|
'post-compile/update-gitweb-access-list',
|
||||||
|
'post-compile/update-git-daemon-access-list',
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# per perl rules, this should be the last line in such a file:
|
||||||
|
1;
|
||||||
|
|
||||||
|
# Local variables:
|
||||||
|
# mode: perl
|
||||||
|
# End:
|
||||||
|
# vim: set syn=perl:
|
167
src/lib/Gitolite/Setup.pm
Normal file
167
src/lib/Gitolite/Setup.pm
Normal file
|
@ -0,0 +1,167 @@
|
||||||
|
package Gitolite::Setup;
|
||||||
|
|
||||||
|
# implements 'gitolite setup'
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
=for args
|
||||||
|
Usage: gitolite setup [<option>]
|
||||||
|
|
||||||
|
Setup gitolite, compile conf, run the POST_COMPILE trigger (see rc file) and
|
||||||
|
propagate hooks.
|
||||||
|
|
||||||
|
-a, --admin <name> admin name
|
||||||
|
-pk, --pubkey <file> pubkey file name
|
||||||
|
-ho, --hooks-only skip other steps and just propagate hooks
|
||||||
|
|
||||||
|
First run: either the pubkey or the admin name is *required*, depending on
|
||||||
|
whether you're using ssh mode or http mode.
|
||||||
|
|
||||||
|
Subsequent runs:
|
||||||
|
|
||||||
|
- Without options, 'gitolite setup' is a general "fix up everything" command
|
||||||
|
(for example, if you brought in repos from outside, or someone messed
|
||||||
|
around with the hooks, or you made an rc file change that affects access
|
||||||
|
rules, etc.)
|
||||||
|
|
||||||
|
- '-pk' can be used to replace the admin key; useful if you lost the admin's
|
||||||
|
private key but do have shell access to the server.
|
||||||
|
|
||||||
|
- '-ho' is mainly for scripting use. Do not combine with other options.
|
||||||
|
|
||||||
|
- '-a' is ignored
|
||||||
|
|
||||||
|
=cut
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
@EXPORT = qw(
|
||||||
|
setup
|
||||||
|
);
|
||||||
|
|
||||||
|
use Exporter 'import';
|
||||||
|
use Getopt::Long;
|
||||||
|
|
||||||
|
use Gitolite::Rc;
|
||||||
|
use Gitolite::Common;
|
||||||
|
use Gitolite::Conf::Store;
|
||||||
|
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
sub setup {
|
||||||
|
my ( $admin, $pubkey, $h_only, $argv ) = args();
|
||||||
|
|
||||||
|
unless ($h_only) {
|
||||||
|
setup_glrc();
|
||||||
|
setup_gladmin( $admin, $pubkey, $argv );
|
||||||
|
|
||||||
|
_system("gitolite compile");
|
||||||
|
_system("gitolite trigger POST_COMPILE");
|
||||||
|
}
|
||||||
|
|
||||||
|
hook_repos(); # all of them, just to be sure
|
||||||
|
}
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
sub args {
|
||||||
|
my $admin = '';
|
||||||
|
my $pubkey = '';
|
||||||
|
my $h_only = 0;
|
||||||
|
my $help = 0;
|
||||||
|
my $argv = join( " ", @ARGV );
|
||||||
|
|
||||||
|
GetOptions(
|
||||||
|
'admin|a=s' => \$admin,
|
||||||
|
'pubkey|pk=s' => \$pubkey,
|
||||||
|
'hooks-only|ho' => \$h_only,
|
||||||
|
'help|h' => \$help,
|
||||||
|
) or usage();
|
||||||
|
|
||||||
|
usage() if $help or ( $pubkey and $admin );
|
||||||
|
usage() if $h_only and ($admin or $pubkey);
|
||||||
|
|
||||||
|
if ($pubkey) {
|
||||||
|
$pubkey =~ /\.pub$/ or _die "'$pubkey' name does not end in .pub";
|
||||||
|
$pubkey =~ /\@/ and _die "'$pubkey' name contains '\@'";
|
||||||
|
tsh_try("cat $pubkey") or _die "'$pubkey' not a readable file";
|
||||||
|
tsh_lines() == 1 or _die "'$pubkey' must have exactly one line";
|
||||||
|
tsh_try("ssh-keygen -l -f $pubkey") or _die "'$pubkey' does not seem to be a valid ssh pubkey file";
|
||||||
|
|
||||||
|
$admin = $pubkey;
|
||||||
|
$admin =~ s(.*/)();
|
||||||
|
$admin =~ s/\.pub$//;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ( $admin || '', $pubkey || '', $h_only || 0, $argv );
|
||||||
|
}
|
||||||
|
|
||||||
|
sub setup_glrc {
|
||||||
|
_print( glrc('default-filename'), glrc('default-text') ) if not glrc('filename');
|
||||||
|
}
|
||||||
|
|
||||||
|
sub setup_gladmin {
|
||||||
|
my ( $admin, $pubkey, $argv ) = @_;
|
||||||
|
_die "'-pk' or '-a' required; see 'gitolite setup -h' for more"
|
||||||
|
if not $admin and not -f "$rc{GL_ADMIN_BASE}/conf/gitolite.conf";
|
||||||
|
|
||||||
|
# reminder: 'admin files' are in ~/.gitolite, 'admin repo' is
|
||||||
|
# $rc{GL_REPO_BASE}/gitolite-admin.git
|
||||||
|
|
||||||
|
# grab the pubkey content before we chdir() away
|
||||||
|
my $pubkey_content = '';
|
||||||
|
$pubkey_content = slurp($pubkey) if $pubkey;
|
||||||
|
|
||||||
|
# set up the admin files in admin-base
|
||||||
|
|
||||||
|
_mkdir( $rc{GL_ADMIN_BASE} );
|
||||||
|
_chdir( $rc{GL_ADMIN_BASE} );
|
||||||
|
|
||||||
|
_mkdir("conf");
|
||||||
|
_mkdir("logs");
|
||||||
|
my $conf;
|
||||||
|
{
|
||||||
|
local $/ = undef;
|
||||||
|
$conf = <DATA>;
|
||||||
|
}
|
||||||
|
$conf =~ s/%ADMIN/$admin/g;
|
||||||
|
|
||||||
|
_print( "conf/gitolite.conf", $conf ) if not -f "conf/gitolite.conf";
|
||||||
|
|
||||||
|
if ($pubkey) {
|
||||||
|
_mkdir("keydir");
|
||||||
|
_print( "keydir/$admin.pub", $pubkey_content );
|
||||||
|
}
|
||||||
|
|
||||||
|
# set up the admin repo in repo-base
|
||||||
|
|
||||||
|
_chdir();
|
||||||
|
_mkdir( $rc{GL_REPO_BASE} );
|
||||||
|
_chdir( $rc{GL_REPO_BASE} );
|
||||||
|
|
||||||
|
new_repo("gitolite-admin") if not -d "gitolite-admin.git";
|
||||||
|
|
||||||
|
# commit the admin files to the admin repo
|
||||||
|
|
||||||
|
$ENV{GIT_WORK_TREE} = $rc{GL_ADMIN_BASE};
|
||||||
|
_chdir("$rc{GL_REPO_BASE}/gitolite-admin.git");
|
||||||
|
_system("git add conf/gitolite.conf");
|
||||||
|
_system("git add keydir") if $pubkey;
|
||||||
|
tsh_try("git config --get user.email") or tsh_run( "git config user.email $ENV{USER}\@" . `hostname` );
|
||||||
|
tsh_try("git config --get user.name") or tsh_run( "git config user.name '$ENV{USER} on '" . `hostname` );
|
||||||
|
tsh_try("git diff --cached --quiet")
|
||||||
|
or tsh_try("git commit -am 'gitolite setup $argv'")
|
||||||
|
or _die "setup failed to commit to the admin repo";
|
||||||
|
delete $ENV{GIT_WORK_TREE};
|
||||||
|
}
|
||||||
|
|
||||||
|
1;
|
||||||
|
|
||||||
|
__DATA__
|
||||||
|
repo gitolite-admin
|
||||||
|
RW+ = %ADMIN
|
||||||
|
|
||||||
|
repo testing
|
||||||
|
RW+ = @all
|
121
src/lib/Gitolite/Test.pm
Normal file
121
src/lib/Gitolite/Test.pm
Normal file
|
@ -0,0 +1,121 @@
|
||||||
|
package Gitolite::Test;
|
||||||
|
|
||||||
|
# functions for the test code to use
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
#<<<
|
||||||
|
@EXPORT = qw(
|
||||||
|
try
|
||||||
|
put
|
||||||
|
text
|
||||||
|
lines
|
||||||
|
dump
|
||||||
|
confreset
|
||||||
|
confadd
|
||||||
|
cmp
|
||||||
|
md5sum
|
||||||
|
);
|
||||||
|
#>>>
|
||||||
|
use Exporter 'import';
|
||||||
|
use File::Path qw(mkpath);
|
||||||
|
use Carp qw(carp cluck croak confess);
|
||||||
|
use Digest::MD5 qw(md5_hex);
|
||||||
|
|
||||||
|
use Gitolite::Common;
|
||||||
|
|
||||||
|
BEGIN {
|
||||||
|
require Gitolite::Test::Tsh;
|
||||||
|
*{'try'} = \&Tsh::try;
|
||||||
|
*{'put'} = \&Tsh::put;
|
||||||
|
*{'text'} = \&Tsh::text;
|
||||||
|
*{'lines'} = \&Tsh::lines;
|
||||||
|
*{'cmp'} = \&Tsh::cmp;
|
||||||
|
}
|
||||||
|
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
# make sure the user is ready for it
|
||||||
|
if (not $ENV{GITOLITE_TEST} or $ENV{GITOLITE_TEST} ne 'y') {
|
||||||
|
print "Bail out! See t/README for information on how to run the tests.\n";
|
||||||
|
exit 255;
|
||||||
|
}
|
||||||
|
|
||||||
|
# required preamble for all tests
|
||||||
|
try "
|
||||||
|
DEF gsh = /TRACE: gsh.SOC=/
|
||||||
|
DEF reject = /hook declined to update/; /remote rejected.*hook declined/; /error: failed to push some refs to/
|
||||||
|
|
||||||
|
DEF AP_1 = cd ../gitolite-admin; ok or die cant find admin repo clone;
|
||||||
|
DEF AP_2 = AP_1; git add conf ; ok; git commit -m %1; ok; /master.* %1/
|
||||||
|
DEF ADMIN_PUSH = AP_2 %1; glt push admin origin; ok; gsh; /master -> master/
|
||||||
|
|
||||||
|
DEF CS_1 = pwd; //tmp/tsh_tempdir.*gitolite-admin/; git remote -v; ok; /file://gitolite-admin/
|
||||||
|
DEF CHECK_SETUP = CS_1; git log; ok; /fa7564c1b903ea3dce49314753f25b34b9e0cea0/
|
||||||
|
|
||||||
|
DEF CLONE = glt clone %1 file:///%2
|
||||||
|
DEF PUSH = glt push %1 origin
|
||||||
|
|
||||||
|
# clean install
|
||||||
|
mkdir -p $ENV{HOME}/bin
|
||||||
|
ln -sf $ENV{PWD}/t/glt ~/bin
|
||||||
|
./install -ln
|
||||||
|
cd; rm -vrf .gito* repositories
|
||||||
|
git config --global user.name \"gitolite tester\"
|
||||||
|
git config --global user.email \"tester\@example.com\"
|
||||||
|
|
||||||
|
# setup
|
||||||
|
gitolite setup -a admin
|
||||||
|
|
||||||
|
# clone admin repo
|
||||||
|
cd tsh_tempdir
|
||||||
|
glt clone admin --progress file://gitolite-admin
|
||||||
|
cd gitolite-admin
|
||||||
|
" or die "could not setup the test environment; errors:\n\n" . text() . "\n\n";
|
||||||
|
|
||||||
|
sub dump {
|
||||||
|
use Data::Dumper;
|
||||||
|
for my $i (@_) {
|
||||||
|
print STDERR "DBG: " . Dumper($i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub _confargs {
|
||||||
|
return @_ if ( $_[1] );
|
||||||
|
return 'gitolite.conf', $_[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
sub confreset {
|
||||||
|
chdir("../gitolite-admin") or die "in `pwd`, could not cd ../g-a";
|
||||||
|
system( "rm", "-rf", "conf" );
|
||||||
|
mkdir("conf");
|
||||||
|
system("mv ~/repositories/gitolite-admin.git ~/repositories/.ga");
|
||||||
|
system("mv ~/repositories/testing.git ~/repositories/.te");
|
||||||
|
system("find ~/repositories -name '*.git' |xargs rm -rf");
|
||||||
|
system("mv ~/repositories/.ga ~/repositories/gitolite-admin.git");
|
||||||
|
system("mv ~/repositories/.te ~/repositories/testing.git ");
|
||||||
|
put "|cut -c9- > conf/gitolite.conf", '
|
||||||
|
repo gitolite-admin
|
||||||
|
RW+ = admin
|
||||||
|
repo testing
|
||||||
|
RW+ = @all
|
||||||
|
';
|
||||||
|
}
|
||||||
|
|
||||||
|
sub confadd {
|
||||||
|
chdir("../gitolite-admin") or die "in `pwd`, could not cd ../g-a";
|
||||||
|
my ( $file, $string ) = _confargs(@_);
|
||||||
|
put "|cat >> conf/$file", $string;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub md5sum {
|
||||||
|
my $out = '';
|
||||||
|
for my $file (@_) {
|
||||||
|
$out .= md5_hex(slurp($file)) . " $file\n";
|
||||||
|
}
|
||||||
|
return $out;
|
||||||
|
}
|
||||||
|
|
||||||
|
1;
|
643
src/lib/Gitolite/Test/Tsh.pm
Normal file
643
src/lib/Gitolite/Test/Tsh.pm
Normal file
|
@ -0,0 +1,643 @@
|
||||||
|
#!/usr/bin/perl
|
||||||
|
use 5.10.0;
|
||||||
|
|
||||||
|
# Tsh -- non interactive Testing SHell in perl
|
||||||
|
|
||||||
|
# TODO items:
|
||||||
|
# - allow an RC file to be used to add basic and extended commands
|
||||||
|
# - convert internal defaults to additions to the RC file
|
||||||
|
# - implement shell commands as you go
|
||||||
|
# - solve the "pass/fail" inconsistency between shell and perl
|
||||||
|
# - solve the pipes problem (use 'overload'?)
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
# modules
|
||||||
|
|
||||||
|
package Tsh;
|
||||||
|
|
||||||
|
use Exporter 'import';
|
||||||
|
@EXPORT = qw(
|
||||||
|
try run cmp AUTOLOAD
|
||||||
|
rc error_count text lines error_list put
|
||||||
|
cd tsh_tempdir
|
||||||
|
|
||||||
|
$HOME $PWD $USER
|
||||||
|
);
|
||||||
|
@EXPORT_OK = qw();
|
||||||
|
|
||||||
|
use Env qw(@PATH HOME PWD USER TSH_VERBOSE);
|
||||||
|
# other candidates:
|
||||||
|
# GL_ADMINDIR GL_BINDIR GL_RC GL_REPO_BASE_ABS GL_REPO GL_USER
|
||||||
|
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
use Text::Tabs; # only used for formatting the usage() message
|
||||||
|
use Text::ParseWords;
|
||||||
|
|
||||||
|
use File::Temp qw(tempdir);
|
||||||
|
END { chdir( $ENV{HOME} ); }
|
||||||
|
# we need this END handler *after* the 'use File::Temp' above. Without
|
||||||
|
# this, if $PWD at exit was $tempdir, you get errors like "cannot remove
|
||||||
|
# path when cwd is [...] at /usr/share/perl5/File/Temp.pm line 902".
|
||||||
|
|
||||||
|
use Data::Dumper;
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
# globals
|
||||||
|
|
||||||
|
my $rc; # return code from backticked (external) programs
|
||||||
|
my $text; # STDOUT+STDERR of backticked (external) programs
|
||||||
|
my $lec; # the last external command (the rc and text are from this)
|
||||||
|
my $cmd; # the current command
|
||||||
|
|
||||||
|
my $testnum; # current test number, for info in TAP output
|
||||||
|
my $testname; # current test name, for error info to user
|
||||||
|
my $line; # current line number and text
|
||||||
|
|
||||||
|
my $err_count; # count of test failures
|
||||||
|
my @errors_in; # list of testnames that errored
|
||||||
|
|
||||||
|
my $tick; # timestamp for git commits
|
||||||
|
|
||||||
|
my %autoloaded;
|
||||||
|
my $tempdir = '';
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
# setup
|
||||||
|
|
||||||
|
# unbuffer STDOUT and STDERR
|
||||||
|
select(STDERR); $|++;
|
||||||
|
select(STDOUT); $|++;
|
||||||
|
|
||||||
|
# set the timestamp (needed only under harness)
|
||||||
|
test_tick() if $ENV{HARNESS_ACTIVE};
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
# this is for one-liner access from outside, using @ARGV, as in:
|
||||||
|
# perl -MTsh -e 'tsh()' 'tsh command list'
|
||||||
|
# or via STDIN
|
||||||
|
# perl -MTsh -e 'tsh()' < file-containing-tsh-commands
|
||||||
|
# NOTE: it **exits**!
|
||||||
|
|
||||||
|
sub tsh {
|
||||||
|
my @lines;
|
||||||
|
|
||||||
|
if (@ARGV) {
|
||||||
|
# simple, single argument which is a readable filename
|
||||||
|
if ( @ARGV == 1 and $ARGV[0] !~ /\s/ and -r $ARGV[0] ) {
|
||||||
|
# take the contents of the file
|
||||||
|
@lines = <>;
|
||||||
|
} else {
|
||||||
|
# more than one argument *or* not readable filename
|
||||||
|
# just take the arguments themselves as the command list
|
||||||
|
@lines = @ARGV;
|
||||||
|
@ARGV = ();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
# no arguments given, take STDIN
|
||||||
|
usage() if -t;
|
||||||
|
@lines = <>;
|
||||||
|
}
|
||||||
|
|
||||||
|
# and process them
|
||||||
|
try(@lines);
|
||||||
|
|
||||||
|
# print error summary by default
|
||||||
|
if ( not defined $TSH_VERBOSE ) {
|
||||||
|
say STDERR "$err_count error(s)" if $err_count;
|
||||||
|
}
|
||||||
|
|
||||||
|
exit $err_count;
|
||||||
|
}
|
||||||
|
|
||||||
|
# these two get called with series of tsh commands, while the autoload,
|
||||||
|
# (later) handles single commands
|
||||||
|
|
||||||
|
sub try {
|
||||||
|
$line = $rc = $err_count = 0;
|
||||||
|
@errors_in = ();
|
||||||
|
|
||||||
|
# break up multiline arguments into separate lines
|
||||||
|
my @lines = map { split /\n/ } @_;
|
||||||
|
|
||||||
|
# and process them
|
||||||
|
rc_lines(@lines);
|
||||||
|
|
||||||
|
# bump err_count if the last command had a non-0 rc (that was apparently not checked).
|
||||||
|
$err_count++ if $rc;
|
||||||
|
|
||||||
|
# finish up...
|
||||||
|
dbg( 1, "$err_count error(s)" ) if $err_count;
|
||||||
|
return ( not $err_count );
|
||||||
|
}
|
||||||
|
|
||||||
|
# run() differs from try() in that
|
||||||
|
# - uses open(), not backticks
|
||||||
|
# - takes only one command, not tsh-things like ok, /patt/ etc
|
||||||
|
# - - if you pass it an array it uses the list form!
|
||||||
|
|
||||||
|
sub run {
|
||||||
|
open( my $fh, "-|", @_ ) or die "tell sitaram $!";
|
||||||
|
local $/ = undef; $text = <$fh>;
|
||||||
|
close $fh; warn "tell sitaram $!" if $!;
|
||||||
|
$rc = ( $? >> 8 );
|
||||||
|
return $text;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub put {
|
||||||
|
my ( $file, $data ) = @_;
|
||||||
|
die "probable quoting error in arguments to put: $file\n" if $file =~ /^\s*['"]/;
|
||||||
|
my $mode = ">";
|
||||||
|
$mode = "|-" if $file =~ s/^\s*\|\s*//;
|
||||||
|
|
||||||
|
$rc = 0;
|
||||||
|
my $fh;
|
||||||
|
open( $fh, $mode, $file )
|
||||||
|
and print $fh $data
|
||||||
|
and close $fh
|
||||||
|
and return 1;
|
||||||
|
|
||||||
|
$rc = 1;
|
||||||
|
dbg( 1, "put $file: $!" );
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
# TODO: AUTOLOAD and exportable convenience subs for common shell commands
|
||||||
|
|
||||||
|
sub cd {
|
||||||
|
my $dir = shift || '';
|
||||||
|
_cd($dir);
|
||||||
|
dbg( 1, "cd $dir: $!" ) if $rc;
|
||||||
|
return ( not $rc );
|
||||||
|
}
|
||||||
|
|
||||||
|
# this is classic AUTOLOAD, almost from the perlsub manpage. Although, if
|
||||||
|
# instead of `ls('bin');` you want to be able to say `ls 'bin';` you will need
|
||||||
|
# to predeclare ls, with `sub ls;`.
|
||||||
|
sub AUTOLOAD {
|
||||||
|
my $program = $Tsh::AUTOLOAD;
|
||||||
|
dbg( 4, "program = $program, arg=$_[0]" );
|
||||||
|
$program =~ s/.*:://;
|
||||||
|
$autoloaded{$program}++;
|
||||||
|
|
||||||
|
die "tsh's autoload support expects only one arg\n" if @_ > 1;
|
||||||
|
_sh("$program $_[0]");
|
||||||
|
return ( not $rc ); # perl truth
|
||||||
|
}
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
# exportable service subs
|
||||||
|
|
||||||
|
sub rc {
|
||||||
|
return $rc || 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub text {
|
||||||
|
return $text || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
sub lines {
|
||||||
|
return split /\n/, $text;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub error_count {
|
||||||
|
return $err_count;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub error_list {
|
||||||
|
return (
|
||||||
|
wantarray
|
||||||
|
? @errors_in
|
||||||
|
: join( "\n", @errors_in )
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub tsh_tempdir {
|
||||||
|
# create tempdir if not already done
|
||||||
|
$tempdir = tempdir( "tsh_tempdir.XXXXXXXXXX", TMPDIR => 1, CLEANUP => 1 ) unless $tempdir;
|
||||||
|
# XXX TODO that 'UNLINK' doesn't work for Ctrl_C
|
||||||
|
|
||||||
|
return $tempdir;
|
||||||
|
}
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
# internal (non-exportable) service subs
|
||||||
|
|
||||||
|
sub print_plan {
|
||||||
|
return unless $ENV{HARNESS_ACTIVE};
|
||||||
|
my $_ = shift;
|
||||||
|
say "1..$_";
|
||||||
|
}
|
||||||
|
|
||||||
|
sub rc_lines {
|
||||||
|
my @lines = @_;
|
||||||
|
|
||||||
|
while (@lines) {
|
||||||
|
my $_ = shift @lines;
|
||||||
|
chomp; $_ = trim_ws($_);
|
||||||
|
|
||||||
|
$line++;
|
||||||
|
|
||||||
|
# this also sets $testname
|
||||||
|
next if is_comment_or_empty($_);
|
||||||
|
|
||||||
|
dbg( 2, "L: $_" );
|
||||||
|
$line .= ": $_"; # save line for printing with 'FAIL:'
|
||||||
|
|
||||||
|
# a DEF has to be on a line by itself
|
||||||
|
if (/^DEF\s+([-.\w]+)\s*=\s*(\S.*)$/) {
|
||||||
|
def( $1, $2 );
|
||||||
|
next;
|
||||||
|
}
|
||||||
|
|
||||||
|
my @cmds = cmds($_);
|
||||||
|
|
||||||
|
# process each command
|
||||||
|
# (note: some of the commands may put stuff back into @lines)
|
||||||
|
while (@cmds) {
|
||||||
|
# this needs to be the 'global' one, since fail() prints it
|
||||||
|
$cmd = shift @cmds;
|
||||||
|
|
||||||
|
# is the current command a "testing" command?
|
||||||
|
my $testing_cmd = (
|
||||||
|
$cmd =~ m(^ok(?:\s+or\s+(.*))?$)
|
||||||
|
or $cmd =~ m(^!ok(?:\s+or\s+(.*))?$)
|
||||||
|
or $cmd =~ m(^/(.*?)/(?:\s+or\s+(.*))?$)
|
||||||
|
or $cmd =~ m(^!/(.*?)/(?:\s+or\s+(.*))?$)
|
||||||
|
);
|
||||||
|
|
||||||
|
# warn if the previous command failed but rc is not being checked
|
||||||
|
if ( $rc and not $testing_cmd ) {
|
||||||
|
dbg( 1, "rc: $rc from cmd prior to '$cmd'\n" );
|
||||||
|
# count this as a failure, for exit status purposes
|
||||||
|
$err_count++;
|
||||||
|
# and reset the rc, otherwise for example 'ls foo; tt; tt; tt'
|
||||||
|
# will tell you there are 3 errors!
|
||||||
|
$rc = 0;
|
||||||
|
push @errors_in, $testname if $testname;
|
||||||
|
}
|
||||||
|
|
||||||
|
# prepare to run the command
|
||||||
|
dbg( 3, "C: $cmd" );
|
||||||
|
if ( def($cmd) ) {
|
||||||
|
# expand macro and replace head of @cmds (unshift)
|
||||||
|
dbg( 2, "DEF: $cmd" );
|
||||||
|
unshift @cmds, cmds( def($cmd) );
|
||||||
|
} else {
|
||||||
|
parse($cmd);
|
||||||
|
}
|
||||||
|
# reset rc if checking is done
|
||||||
|
$rc = 0 if $testing_cmd;
|
||||||
|
# assumes you will (a) never have *both* 'ok' and '!ok' after
|
||||||
|
# an action command, and (b) one of them will come immediately
|
||||||
|
# after the action command, with /patt/ only after it.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub def {
|
||||||
|
my ( $cmd, $list ) = @_;
|
||||||
|
state %def;
|
||||||
|
%def = read_rc_file() unless %def;
|
||||||
|
|
||||||
|
if ($list) {
|
||||||
|
# set mode
|
||||||
|
die "attempt to redefine macro $cmd\n" if $def{$cmd};
|
||||||
|
$def{$cmd} = $list;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
# get mode: split the $cmd at spaces, see if there is a definition
|
||||||
|
# available, substitute any %1, %2, etc., in it and send it back
|
||||||
|
my ( $c, @d ) = shellwords($cmd);
|
||||||
|
my $e; # the expanded value
|
||||||
|
if ( $e = $def{$c} ) { # starting value
|
||||||
|
for my $i ( 1 .. 9 ) {
|
||||||
|
last unless $e =~ /%$i/; # no more %N's (we assume sanity)
|
||||||
|
die "$def{$c} requires more arguments\n" unless @d;
|
||||||
|
my $f = shift @d; # get the next datum
|
||||||
|
$e =~ s/%$i/$f/g; # and substitute %N all over
|
||||||
|
}
|
||||||
|
return join( " ", $e, @d ); # join up any remaining data
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
sub _cd {
|
||||||
|
my $dir = shift || $HOME;
|
||||||
|
# a directory name of 'tsh_tempdir' is special
|
||||||
|
$dir = tsh_tempdir() if $dir eq 'tsh_tempdir';
|
||||||
|
$rc = 0;
|
||||||
|
chdir($dir) or $rc = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub _sh {
|
||||||
|
my $cmd = shift;
|
||||||
|
# TODO: switch to IPC::Open3 or something...?
|
||||||
|
|
||||||
|
dbg( 4, " running: ( $cmd ) 2>&1" );
|
||||||
|
$text = `( $cmd ) 2>&1; /bin/echo -n RC=\$?`;
|
||||||
|
$lec = $cmd;
|
||||||
|
dbg( 4, " results:\n$text" );
|
||||||
|
|
||||||
|
if ( $text =~ /RC=(\d+)$/ ) {
|
||||||
|
$rc = $1;
|
||||||
|
$text =~ s/RC=\d+$//;
|
||||||
|
} else {
|
||||||
|
die "couldnt find RC= in result; this should not happen:\n$text\n\n...\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub _perl {
|
||||||
|
my $perl = shift;
|
||||||
|
local $_;
|
||||||
|
$_ = $text;
|
||||||
|
|
||||||
|
dbg( 4, " eval: $perl" );
|
||||||
|
my $evrc = eval $perl;
|
||||||
|
|
||||||
|
if ($@) {
|
||||||
|
$rc = 1; # shell truth
|
||||||
|
dbg( 1, $@ );
|
||||||
|
# leave $text unchanged
|
||||||
|
} else {
|
||||||
|
$rc = not $evrc;
|
||||||
|
# $rc is always shell truth, so we need to cover the case where
|
||||||
|
# there was no error but it still returned a perl false
|
||||||
|
$text = $_;
|
||||||
|
}
|
||||||
|
dbg( 4, " eval-rc=$evrc, results:\n$text" );
|
||||||
|
}
|
||||||
|
|
||||||
|
sub parse {
|
||||||
|
my $cmd = shift;
|
||||||
|
|
||||||
|
if ( $cmd =~ /^sh (.*)/ ) {
|
||||||
|
|
||||||
|
_sh($1);
|
||||||
|
|
||||||
|
} elsif ( $cmd =~ /^perl (.*)/ ) {
|
||||||
|
|
||||||
|
_perl($1);
|
||||||
|
|
||||||
|
} elsif ( $cmd eq 'tt' or $cmd eq 'test-tick' ) {
|
||||||
|
|
||||||
|
test_tick();
|
||||||
|
|
||||||
|
} elsif ( $cmd =~ /^plan ?(\d+)$/ ) {
|
||||||
|
|
||||||
|
print_plan($1);
|
||||||
|
|
||||||
|
} elsif ( $cmd =~ /^cd ?(\S*)$/ ) {
|
||||||
|
|
||||||
|
_cd($1);
|
||||||
|
|
||||||
|
} elsif ( $cmd =~ /^ENV (\w+)=['"]?(.+?)['"]?$/ ) {
|
||||||
|
|
||||||
|
$ENV{$1} = $2;
|
||||||
|
|
||||||
|
} elsif ( $cmd =~ /^(?:tc|test-commit)\s+(\S.*)$/ ) {
|
||||||
|
|
||||||
|
# this is the only "git special" really; the default expansions are
|
||||||
|
# just that -- defaults. But this one is hardwired!
|
||||||
|
dummy_commits($1);
|
||||||
|
|
||||||
|
} elsif ( $cmd =~ '^put(?:\s+(\S.*))?$' ) {
|
||||||
|
|
||||||
|
if ($1) {
|
||||||
|
put( $1, $text );
|
||||||
|
} else {
|
||||||
|
print $text if defined $text;
|
||||||
|
}
|
||||||
|
|
||||||
|
} elsif ( $cmd =~ m(^ok(?:\s+or\s+(.*))?$) ) {
|
||||||
|
|
||||||
|
$rc ? fail( "ok, rc=$rc from $lec", $1 || '' ) : ok();
|
||||||
|
|
||||||
|
} elsif ( $cmd =~ m(^!ok(?:\s+or\s+(.*))?$) ) {
|
||||||
|
|
||||||
|
$rc ? ok() : fail( "!ok, rc=0 from $lec", $1 || '' );
|
||||||
|
|
||||||
|
} elsif ( $cmd =~ m(^/(.*?)/(?:\s+or\s+(.*))?$) ) {
|
||||||
|
|
||||||
|
expect( $1, $2 );
|
||||||
|
|
||||||
|
} elsif ( $cmd =~ m(^!/(.*?)/(?:\s+or\s+(.*))?$) ) {
|
||||||
|
|
||||||
|
not_expect( $1, $2 );
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
_sh($cmd);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# currently unused
|
||||||
|
sub executable {
|
||||||
|
my $cmd = shift;
|
||||||
|
# path supplied
|
||||||
|
$cmd =~ m(/) and -x $cmd and return 1;
|
||||||
|
# barename; look up in $PATH
|
||||||
|
for my $p (@PATH) {
|
||||||
|
-x "$p/$cmd" and return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub ok {
|
||||||
|
$testnum++;
|
||||||
|
say "ok ($testnum)" if $ENV{HARNESS_ACTIVE};
|
||||||
|
}
|
||||||
|
|
||||||
|
sub fail {
|
||||||
|
$testnum++;
|
||||||
|
say "not ok ($testnum)" if $ENV{HARNESS_ACTIVE};
|
||||||
|
|
||||||
|
my $die = 0;
|
||||||
|
my ( $msg1, $msg2 ) = @_;
|
||||||
|
if ($msg2) {
|
||||||
|
# if arg2 is non-empty, print it regardless of debug level
|
||||||
|
$die = 1 if $msg2 =~ s/^die //;
|
||||||
|
say STDERR "# $msg2";
|
||||||
|
}
|
||||||
|
|
||||||
|
local $TSH_VERBOSE = 1 if $ENV{TSH_ERREXIT};
|
||||||
|
dbg( 1, "FAIL: $msg1", $testname || '', "test number $testnum", "L: $line", "results:\n$text" );
|
||||||
|
|
||||||
|
# count the error and add the testname to the list if it is set
|
||||||
|
$err_count++;
|
||||||
|
push @errors_in, $testname if $testname;
|
||||||
|
|
||||||
|
return unless $die or $ENV{TSH_ERREXIT};
|
||||||
|
dbg( 1, "exiting at cmd $cmd\n" );
|
||||||
|
|
||||||
|
exit( $rc || 74 );
|
||||||
|
}
|
||||||
|
|
||||||
|
sub cmp {
|
||||||
|
# compare input string with second input string or text()
|
||||||
|
my $in = shift;
|
||||||
|
my $text = ( @_ ? +shift : text() );
|
||||||
|
|
||||||
|
if ( $text eq $in ) {
|
||||||
|
ok();
|
||||||
|
} else {
|
||||||
|
fail( 'cmp failed', '' );
|
||||||
|
dbg( 4, "\n\ntext = <<<$text>>>, in = <<<$in>>>\n\n" );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub expect {
|
||||||
|
my ( $patt, $msg ) = @_;
|
||||||
|
$msg =~ s/^\s+// if $msg;
|
||||||
|
my $sm;
|
||||||
|
if ( $sm = sm($patt) ) {
|
||||||
|
dbg( 4, " M: $sm" );
|
||||||
|
ok();
|
||||||
|
} else {
|
||||||
|
fail( "/$patt/", $msg || '' );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub not_expect {
|
||||||
|
my ( $patt, $msg ) = @_;
|
||||||
|
$msg =~ s/^\s+// if $msg;
|
||||||
|
my $sm;
|
||||||
|
if ( $sm = sm($patt) ) {
|
||||||
|
dbg( 4, " M: $sm" );
|
||||||
|
fail( "!/$patt/", $msg || '' );
|
||||||
|
} else {
|
||||||
|
ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub sm {
|
||||||
|
# smart match? for now we just do regex match
|
||||||
|
my $patt = shift;
|
||||||
|
|
||||||
|
return ( $text =~ qr($patt) ? $& : "" );
|
||||||
|
}
|
||||||
|
|
||||||
|
sub trim_ws {
|
||||||
|
my $_ = shift;
|
||||||
|
s/^\s+//; s/\s+$//;
|
||||||
|
return $_;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub is_comment_or_empty {
|
||||||
|
my $_ = shift;
|
||||||
|
chomp; $_ = trim_ws($_);
|
||||||
|
if (/^##\s(.*)/) {
|
||||||
|
$testname = $1;
|
||||||
|
say "# $1";
|
||||||
|
}
|
||||||
|
return ( /^#/ or /^$/ );
|
||||||
|
}
|
||||||
|
|
||||||
|
sub cmds {
|
||||||
|
my $_ = shift;
|
||||||
|
chomp; $_ = trim_ws($_);
|
||||||
|
|
||||||
|
# split on unescaped ';'s, then unescape the ';' in the results
|
||||||
|
my @cmds = map { s/\\;/;/g; $_ } split /(?<!\\);/;
|
||||||
|
@cmds = grep { $_ = trim_ws($_); /\S/; } @cmds;
|
||||||
|
return @cmds;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub dbg {
|
||||||
|
return unless $TSH_VERBOSE;
|
||||||
|
my $level = shift;
|
||||||
|
return unless $TSH_VERBOSE >= $level;
|
||||||
|
my $all = join( "\n", grep( /./, @_ ) );
|
||||||
|
chomp($all);
|
||||||
|
$all =~ s/\n/\n\t/g;
|
||||||
|
say STDERR "# $all";
|
||||||
|
}
|
||||||
|
|
||||||
|
sub ddump {
|
||||||
|
for my $i (@_) {
|
||||||
|
print STDERR "DBG: " . Dumper($i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub usage {
|
||||||
|
# TODO
|
||||||
|
print "Please see documentation at:
|
||||||
|
|
||||||
|
https://github.com/sitaramc/tsh/blob/master/README.mkd
|
||||||
|
|
||||||
|
Meanwhile, here are your local 'macro' definitions:
|
||||||
|
|
||||||
|
";
|
||||||
|
my %m = read_rc_file();
|
||||||
|
my @m = map { "$_\t$m{$_}\n" } sort keys %m;
|
||||||
|
$tabstop = 16;
|
||||||
|
print join( "", expand(@m) );
|
||||||
|
exit 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
# git-specific internal service subs
|
||||||
|
|
||||||
|
sub dummy_commits {
|
||||||
|
for my $f ( split ' ', shift ) {
|
||||||
|
if ( $f eq 'tt' or $f eq 'test-tick' ) {
|
||||||
|
test_tick();
|
||||||
|
next;
|
||||||
|
}
|
||||||
|
my $ts = ( $tick ? gmtime( $tick + 19800 ) : gmtime() );
|
||||||
|
_sh("echo $f at $ts >> $f && git add $f && git commit -m '$f at $ts'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub test_tick {
|
||||||
|
unless ( $ENV{HARNESS_ACTIVE} ) {
|
||||||
|
sleep 1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$tick += 60 if $tick;
|
||||||
|
$tick ||= 1310000000;
|
||||||
|
$ENV{GIT_COMMITTER_DATE} = "$tick +0530";
|
||||||
|
$ENV{GIT_AUTHOR_DATE} = "$tick +0530";
|
||||||
|
}
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
# the internal macros, for easy reference and reading
|
||||||
|
|
||||||
|
sub read_rc_file {
|
||||||
|
my $rcfile = "$HOME/.tshrc";
|
||||||
|
my $rctext;
|
||||||
|
if ( -r $rcfile ) {
|
||||||
|
local $/ = undef;
|
||||||
|
open( my $rcfh, "<", $rcfile ) or die "this should not happen: $!\n";
|
||||||
|
$rctext = <$rcfh>;
|
||||||
|
} else {
|
||||||
|
# this is the default "rc" content
|
||||||
|
$rctext = "
|
||||||
|
add = git add
|
||||||
|
branch = git branch
|
||||||
|
clone = git clone
|
||||||
|
checkout = git checkout
|
||||||
|
commit = git commit
|
||||||
|
fetch = git fetch
|
||||||
|
init = git init
|
||||||
|
push = git push
|
||||||
|
reset = git reset
|
||||||
|
tag = git tag
|
||||||
|
|
||||||
|
empty = git commit --allow-empty -m empty
|
||||||
|
push-om = git push origin master
|
||||||
|
reset-h = git reset --hard
|
||||||
|
reset-hu = git reset --hard \@{u}
|
||||||
|
"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ignore everything except lines of the form "aa = bb cc dd"
|
||||||
|
my %commands = ( $rctext =~ /^\s*([-.\w]+)\s*=\s*(\S.*)$/gm );
|
||||||
|
return %commands;
|
||||||
|
}
|
||||||
|
|
||||||
|
1;
|
33
src/lib/Gitolite/Triggers.pm
Normal file
33
src/lib/Gitolite/Triggers.pm
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
package Gitolite::Triggers;
|
||||||
|
|
||||||
|
# load and run triggered modules
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
#<<<
|
||||||
|
@EXPORT = qw(
|
||||||
|
);
|
||||||
|
#>>>
|
||||||
|
use Exporter 'import';
|
||||||
|
|
||||||
|
use Gitolite::Rc;
|
||||||
|
use Gitolite::Common;
|
||||||
|
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
sub run {
|
||||||
|
my ( $module, $sub, @args ) = @_;
|
||||||
|
$module = "Gitolite::Triggers::$module" if $module !~ /^Gitolite::/;
|
||||||
|
|
||||||
|
eval "require $module";
|
||||||
|
_die "$@" if $@;
|
||||||
|
my $subref;
|
||||||
|
eval "\$subref = \\\&$module" . "::" . "$sub";
|
||||||
|
_die "module '$module' does not exist or does not have sub '$sub'" unless ref($subref) eq 'CODE';
|
||||||
|
|
||||||
|
$subref->(@args);
|
||||||
|
}
|
||||||
|
|
||||||
|
1;
|
81
src/lib/Gitolite/Triggers/Alias.pm
Normal file
81
src/lib/Gitolite/Triggers/Alias.pm
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
package Gitolite::Triggers::Alias;
|
||||||
|
|
||||||
|
use Gitolite::Rc;
|
||||||
|
use Gitolite::Common;
|
||||||
|
use Gitolite::Conf::Load;
|
||||||
|
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
# aliasing a repo to another
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
=for usage
|
||||||
|
|
||||||
|
Why:
|
||||||
|
|
||||||
|
We had an existing repo "foo" that lots of people use. We wanted to
|
||||||
|
rename it to "foo/code", so that related repos "foo/upstream" and
|
||||||
|
"foo/docs" (both containing stuff we did not want to put in "foo") could
|
||||||
|
also be made and then the whole thing would be structured nicely.
|
||||||
|
|
||||||
|
At the same time we did not want to *force* all the users to change the
|
||||||
|
name. At least git operations should still work with the old name,
|
||||||
|
although it is OK for "info" and other "commands" to display/require the
|
||||||
|
proper name (i.e., the new name).
|
||||||
|
|
||||||
|
How:
|
||||||
|
|
||||||
|
* add a new variable REPO_ALIASES to the rc file, with entries like:
|
||||||
|
|
||||||
|
REPO_ALIASES =>
|
||||||
|
{
|
||||||
|
'foo' => 'foo/code',
|
||||||
|
}
|
||||||
|
|
||||||
|
* add the following line to the INPUT section in the rc file:
|
||||||
|
|
||||||
|
'Alias::input',
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
|
||||||
|
* only git operations (clone/fetch/push) are alias aware. Nothing else in
|
||||||
|
gitolite, such as all the gitolite commands etc., are alias-aware and will
|
||||||
|
always use/require the proper repo name.
|
||||||
|
|
||||||
|
* http mode has not been tested and will not be. If someone has the time to
|
||||||
|
test it and make it work please let me know.
|
||||||
|
|
||||||
|
* funnily enough, this even works with mirroring! That is, a master can
|
||||||
|
push a repo "foo" to a slave per its configuration, while the slave thinks
|
||||||
|
it is getting repo "bar" from the master per its configuration.
|
||||||
|
|
||||||
|
Just make sure to put the Alias::input line *before* the Mirroring::input
|
||||||
|
line in the rc file on the slave.
|
||||||
|
|
||||||
|
However, it will probably not work with redirected pushes unless you setup
|
||||||
|
the opposite alias ("bar" -> "foo") on master.
|
||||||
|
=cut
|
||||||
|
|
||||||
|
sub input {
|
||||||
|
my $git_commands = "git-upload-pack|git-receive-pack|git-upload-archive";
|
||||||
|
my $user = $ARGV[0] || '@all'; # user name is undocumented for now
|
||||||
|
|
||||||
|
if ( $ENV{SSH_ORIGINAL_COMMAND} =~ /(?:$git_commands) '\/?(\S+)'$/ ) {
|
||||||
|
my $repo = $1;
|
||||||
|
( my $norm = $repo ) =~ s/\.git$//; # normalised repo name
|
||||||
|
|
||||||
|
my $target;
|
||||||
|
|
||||||
|
return unless $target = $rc{REPO_ALIASES}{$norm};
|
||||||
|
$target = $target->{$user} if ref($target) eq 'HASH';
|
||||||
|
return unless $target;
|
||||||
|
|
||||||
|
_warn "'$norm' is an alias for '$target'";
|
||||||
|
|
||||||
|
$ENV{SSH_ORIGINAL_COMMAND} =~ s/'\/?$repo'/'$target'/;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
1;
|
24
src/lib/Gitolite/Triggers/AutoCreate.pm
Normal file
24
src/lib/Gitolite/Triggers/AutoCreate.pm
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
package Gitolite::Triggers::AutoCreate;
|
||||||
|
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
# perl trigger set for stuff to do with auto-creating repos
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
# to deny auto-create on read access, add 'AutoCreate::deny_R' to the
|
||||||
|
# PRE_CREATE trigger list
|
||||||
|
sub deny_R {
|
||||||
|
die "autocreate denied\n" if $_[3] and $_[3] eq 'R';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
# to deny auto-create on read *and* write access, add 'AutoCreate::deny_RW' to
|
||||||
|
# the PRE_CREATE trigger list. This means you can only create repos using the
|
||||||
|
# 'create' command, (which needs to be enabled in the COMMANDS list).
|
||||||
|
sub deny_RW {
|
||||||
|
die "autocreate denied\n" if $_[3] and ( $_[3] eq 'R' or $_[3] eq 'W' );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
1;
|
52
src/lib/Gitolite/Triggers/CpuTime.pm
Normal file
52
src/lib/Gitolite/Triggers/CpuTime.pm
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
package Gitolite::Triggers::CpuTime;
|
||||||
|
|
||||||
|
use Time::HiRes;
|
||||||
|
|
||||||
|
use Gitolite::Rc;
|
||||||
|
use Gitolite::Common;
|
||||||
|
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
# cpu and elapsed times for gitolite+git operations
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
# uncomment the appropriate lines in the rc file to enable this
|
||||||
|
|
||||||
|
# Ideally, you will (a) write your own code with a different filename so later
|
||||||
|
# gitolite upgrades won't overwrite your copy, (b) add appropriate variables
|
||||||
|
# to the rc file, and (c) change your rc file to call your program instead.
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
my $start_time;
|
||||||
|
|
||||||
|
sub input {
|
||||||
|
_warn "something wrong with the invocation of CpuTime::input" if $ENV{GL_TID} ne $$;
|
||||||
|
$start_time = [ Time::HiRes::gettimeofday() ];
|
||||||
|
}
|
||||||
|
|
||||||
|
sub post_git {
|
||||||
|
_warn "something wrong with the invocation of CpuTime::post_git" if $ENV{GL_TID} ne $$;
|
||||||
|
|
||||||
|
my ( $trigger, $repo, $user, $aa, $ref, $verb ) = @_;
|
||||||
|
my ( $utime, $stime, $cutime, $cstime ) = times();
|
||||||
|
my $elapsed = Time::HiRes::tv_interval($start_time);
|
||||||
|
|
||||||
|
gl_log( 'times', $utime, $stime, $cutime, $cstime, $elapsed );
|
||||||
|
|
||||||
|
# now do whatever you want with the data; the following is just an example.
|
||||||
|
|
||||||
|
if ( my $limit = $rc{CPU_TIME_WARN_LIMIT} ) {
|
||||||
|
my $total = $utime + $cutime + $stime + $cstime;
|
||||||
|
# some code to send an email or whatever...
|
||||||
|
say2 "limit = $limit, actual = $total" if $total > $limit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( $rc{DISPLAY_CPU_TIME} ) {
|
||||||
|
say2 "perf stats for $verb on repo '$repo':";
|
||||||
|
say2 " user CPU time: " . ( $utime + $cutime );
|
||||||
|
say2 " sys CPU time: " . ( $stime + $cstime );
|
||||||
|
say2 " elapsed time: " . $elapsed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
1;
|
232
src/lib/Gitolite/Triggers/Mirroring.pm
Normal file
232
src/lib/Gitolite/Triggers/Mirroring.pm
Normal file
|
@ -0,0 +1,232 @@
|
||||||
|
package Gitolite::Triggers::Mirroring;
|
||||||
|
|
||||||
|
use Gitolite::Rc;
|
||||||
|
use Gitolite::Common;
|
||||||
|
use Gitolite::Conf::Load;
|
||||||
|
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
my $git_commands = "git-upload-pack|git-receive-pack|git-upload-archive";
|
||||||
|
my $hn = $rc{HOSTNAME};
|
||||||
|
|
||||||
|
my ( $mode, $master, %slaves, %trusted_slaves );
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
sub input {
|
||||||
|
unless ($ARGV[0] =~ /^server-(\S+)$/) {
|
||||||
|
_die "'$ARGV[0]' is not a valid server name" if $ENV{SSH_ORIGINAL_COMMAND} =~ /^USER=(\S+) SOC=(git-receive-pack '(\S+)')$/;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
# note: we treat %rc as our own internal "poor man's %ENV"
|
||||||
|
$rc{FROM_SERVER} = $1;
|
||||||
|
trace( 3, "from_server: $1" );
|
||||||
|
my $sender = $rc{FROM_SERVER} || '';
|
||||||
|
|
||||||
|
# custom peer-to-peer commands. At present the only one is 'perms -c',
|
||||||
|
# sent from a mirror command
|
||||||
|
if ($ENV{SSH_ORIGINAL_COMMAND} =~ /^CREATOR=(\S+) perms -c '(\S+)'$/) {
|
||||||
|
$ENV{GL_USER} = $1;
|
||||||
|
|
||||||
|
my $repo = $2;
|
||||||
|
details($repo);
|
||||||
|
_die "$hn: '$repo' is local" if $mode eq 'local';
|
||||||
|
_die "$hn: '$repo' is native" if $mode eq 'master';
|
||||||
|
_die "$hn: '$sender' is not the master for '$repo'" if $master ne $sender;
|
||||||
|
|
||||||
|
# this expects valid perms content on STDIN
|
||||||
|
_system("gitolite perms -c $repo");
|
||||||
|
|
||||||
|
# we're done. Yes, really...
|
||||||
|
exit 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( $ENV{SSH_ORIGINAL_COMMAND} =~ /^USER=(\S+) SOC=(git-receive-pack '(\S+)')$/ ) {
|
||||||
|
# my ($user, $newsoc, $repo) = ($1, $2, $3);
|
||||||
|
$ENV{SSH_ORIGINAL_COMMAND} = $2;
|
||||||
|
@ARGV = ($1);
|
||||||
|
$rc{REDIRECTED_PUSH} = 1;
|
||||||
|
trace( 3, "redirected_push for user $1" );
|
||||||
|
} else {
|
||||||
|
# master -> slave push, no access checks needed
|
||||||
|
$ENV{GL_BYPASS_ACCESS_CHECKS} = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
sub pre_git {
|
||||||
|
return unless $hn;
|
||||||
|
# nothing, and I mean NOTHING, happens if HOSTNAME is not set
|
||||||
|
trace( 1, "pre_git() on $hn" );
|
||||||
|
|
||||||
|
my ( $repo, $user, $aa ) = @_[ 1, 2, 3 ];
|
||||||
|
|
||||||
|
my $sender = $rc{FROM_SERVER} || '';
|
||||||
|
$user = '' if $sender and not exists $rc{REDIRECTED_PUSH};
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# now you know the repo, get its mirroring details
|
||||||
|
details($repo);
|
||||||
|
|
||||||
|
# we don't deal with any reads. Note that for pre-git this check must
|
||||||
|
# happen *after* getting details, to give mode() a chance to die on "known
|
||||||
|
# unknown" repos (repos that are in the config, but mirror settings
|
||||||
|
# exclude this host from both the master and slave lists)
|
||||||
|
return if $aa eq 'R';
|
||||||
|
|
||||||
|
trace( 1, "mirror", "pre_git", $repo, "user=$user", "sender=$sender", "mode=$mode", ( $rc{REDIRECTED_PUSH} ? ("redirected") : () ) );
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# case 1: we're master or slave, normal user pushing to us
|
||||||
|
if ( $user and not $rc{REDIRECTED_PUSH} ) {
|
||||||
|
trace( 3, "case 1, user push" );
|
||||||
|
return if $mode eq 'local' or $mode eq 'master';
|
||||||
|
if ( $trusted_slaves{$hn} ) {
|
||||||
|
trace( 3, "redirecting to $master" );
|
||||||
|
trace( 1, "redirect to $master" );
|
||||||
|
exec( "ssh", $master, "USER=$user", "SOC=$ENV{SSH_ORIGINAL_COMMAND}" );
|
||||||
|
} else {
|
||||||
|
_die "$hn: pushing '$repo' to slave '$hn' not allowed";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# case 2: we're slave, master pushing to us
|
||||||
|
if ( $sender and not $rc{REDIRECTED_PUSH} ) {
|
||||||
|
trace( 3, "case 2, master push" );
|
||||||
|
_die "$hn: '$repo' is local" if $mode eq 'local';
|
||||||
|
_die "$hn: '$repo' is native" if $mode eq 'master';
|
||||||
|
_die "$hn: '$sender' is not the master for '$repo'" if $master ne $sender;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# case 3: we're master, slave sending a redirected push to us
|
||||||
|
if ( $sender and $rc{REDIRECTED_PUSH} ) {
|
||||||
|
trace( 3, "case 2, slave redirect" );
|
||||||
|
_die "$hn: '$repo' is local" if $mode eq 'local';
|
||||||
|
_die "$hn: '$repo' is not native" if $mode eq 'slave';
|
||||||
|
_die "$hn: '$sender' is not a valid slave for '$repo'" if not $slaves{$sender};
|
||||||
|
_die "$hn: redirection not allowed from '$sender'" if not $trusted_slaves{$sender};
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_die "$hn: should not reach this line";
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
sub post_git {
|
||||||
|
return unless $hn;
|
||||||
|
# nothing, and I mean NOTHING, happens if HOSTNAME is not set
|
||||||
|
trace( 1, "post_git() on $hn" );
|
||||||
|
|
||||||
|
my ( $repo, $user, $aa ) = @_[ 1, 2, 3 ];
|
||||||
|
# we don't deal with any reads
|
||||||
|
return if $aa eq 'R';
|
||||||
|
|
||||||
|
my $sender = $rc{FROM_SERVER} || '';
|
||||||
|
$user = '' if $sender;
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# now you know the repo, get its mirroring details
|
||||||
|
details($repo);
|
||||||
|
|
||||||
|
trace( 1, "mirror", "post_git", $repo, "user=$user", "sender=$sender", "mode=$mode", ( $rc{REDIRECTED_PUSH} ? ("redirected") : () ) );
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# case 1: we're master or slave, normal user pushing to us
|
||||||
|
if ( $user and not $rc{REDIRECTED_PUSH} ) {
|
||||||
|
trace( 3, "case 1, user push" );
|
||||||
|
return if $mode eq 'local';
|
||||||
|
# slave was eliminated earlier anyway, so that leaves 'master'
|
||||||
|
|
||||||
|
# find all slaves and push to each of them
|
||||||
|
push_to_slaves($repo);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# case 2: we're slave, master pushing to us
|
||||||
|
if ( $sender and not $rc{REDIRECTED_PUSH} ) {
|
||||||
|
trace( 3, "case 2, master push" );
|
||||||
|
# nothing to do
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# case 3: we're master, slave sending a redirected push to us
|
||||||
|
if ( $sender and $rc{REDIRECTED_PUSH} ) {
|
||||||
|
trace( 3, "case 2, slave redirect" );
|
||||||
|
|
||||||
|
# find all slaves and push to each of them
|
||||||
|
push_to_slaves($repo);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
my $lastrepo = '';
|
||||||
|
|
||||||
|
sub details {
|
||||||
|
my $repo = shift;
|
||||||
|
return if $lastrepo eq $repo;
|
||||||
|
|
||||||
|
$master = master($repo);
|
||||||
|
%slaves = slaves($repo);
|
||||||
|
$mode = mode($repo);
|
||||||
|
%trusted_slaves = trusted_slaves($repo);
|
||||||
|
trace( 3, $master, $mode, join( ",", sort keys %slaves ), join( ",", sort keys %trusted_slaves ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
sub master {
|
||||||
|
return option( +shift, 'mirror.master' );
|
||||||
|
}
|
||||||
|
|
||||||
|
sub slaves {
|
||||||
|
my $ref = git_config( +shift, "^gitolite-options\\.mirror\\.slaves.*" );
|
||||||
|
my %out = map { $_ => 1 } map { split } values %$ref;
|
||||||
|
return %out;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub trusted_slaves {
|
||||||
|
my $ref = git_config( +shift, "^gitolite-options\\.mirror\\.redirectOK.*" );
|
||||||
|
# the list of trusted slaves (where we accept redirected pushes from)
|
||||||
|
# is either explicitly given...
|
||||||
|
my @out = map { split } values %$ref;
|
||||||
|
my %out = map { $_ => 1 } @out;
|
||||||
|
# ...or it's all the slaves mentioned if the list is just a "all"
|
||||||
|
%out = %slaves if ( @out == 1 and $out[0] eq 'all' );
|
||||||
|
return %out;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub mode {
|
||||||
|
my $repo = shift;
|
||||||
|
return 'local' if not $hn;
|
||||||
|
return 'master' if $master eq $hn;
|
||||||
|
return 'slave' if $slaves{$hn};
|
||||||
|
return 'local' if not $master and not %slaves;
|
||||||
|
_die "$hn: '$repo' is mirrored but not here";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub push_to_slaves {
|
||||||
|
my $repo = shift;
|
||||||
|
|
||||||
|
my $u = $ENV{GL_USER};
|
||||||
|
delete $ENV{GL_USER}; # why? see src/commands/mirror
|
||||||
|
|
||||||
|
for my $s ( sort keys %slaves ) {
|
||||||
|
system("gitolite mirror push $s $repo &");
|
||||||
|
}
|
||||||
|
|
||||||
|
$ENV{GL_USER} = $u;
|
||||||
|
}
|
||||||
|
|
||||||
|
1;
|
80
src/lib/Gitolite/Triggers/RefexExpr.pm
Normal file
80
src/lib/Gitolite/Triggers/RefexExpr.pm
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
package Gitolite::Triggers::RefexExpr;
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
# track refexes passed and evaluate expressions on them
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
# see instructions for use at the bottom of src/VREF/refex-expr
|
||||||
|
|
||||||
|
use Gitolite::Easy;
|
||||||
|
|
||||||
|
my %passed;
|
||||||
|
my %rules;
|
||||||
|
my $init_done = 0;
|
||||||
|
|
||||||
|
sub access_2 {
|
||||||
|
# get out quick for repos that don't have any rules
|
||||||
|
return if $init_done and not %rules;
|
||||||
|
|
||||||
|
# but we don't really know that the first time, heh!
|
||||||
|
if (not $init_done) {
|
||||||
|
my $repo = $_[1];
|
||||||
|
init($repo);
|
||||||
|
return unless %rules;
|
||||||
|
}
|
||||||
|
|
||||||
|
my $refex = $_[5];
|
||||||
|
return if $refex =~ /DENIED/;
|
||||||
|
|
||||||
|
$passed{$refex}++;
|
||||||
|
|
||||||
|
# evaluate the rules each time; it's not very expensive
|
||||||
|
for my $k (sort keys %rules) {
|
||||||
|
$ENV{"GL_REFEX_EXPR_" . $k} = eval_rule($rules{$k});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub eval_rule {
|
||||||
|
my $rule = shift;
|
||||||
|
|
||||||
|
my $e;
|
||||||
|
$e = join " ", map { convert($_) } split ' ', $rule;
|
||||||
|
|
||||||
|
my $ret = eval $e;
|
||||||
|
_die "eval '$e' -> '$@'" if $@;
|
||||||
|
Gitolite::Common::trace(1, "RefexExpr", "'$rule' -> '$e' -> '$ret'");
|
||||||
|
|
||||||
|
return "'$rule' -> '$e'" if $ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
my %constant;
|
||||||
|
%constant = map { $_ => $_ } qw(1 not and or xor + - ==);
|
||||||
|
$constant{'-lt'} = '<';
|
||||||
|
$constant{'-gt'} = '>';
|
||||||
|
$constant{'-eq'} = '==';
|
||||||
|
$constant{'-le'} = '<=';
|
||||||
|
$constant{'-ge'} = '>=';
|
||||||
|
$constant{'-ne'} = '!=';
|
||||||
|
|
||||||
|
sub convert {
|
||||||
|
my $i = shift;
|
||||||
|
return $i if $i =~ /^-?\d+$/;
|
||||||
|
return $constant{$i} || $passed{$i} || $passed{"refs/heads/$i"} || 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
# called only once
|
||||||
|
sub init {
|
||||||
|
$init_done = 1;
|
||||||
|
my $repo = shift;
|
||||||
|
|
||||||
|
# find all the rule expressions
|
||||||
|
my %t = config($repo, "^gitolite-options\\.refex-expr\\.");
|
||||||
|
my ($k, $v);
|
||||||
|
# get rid of the cruft and store just the rule name as the key
|
||||||
|
while ( ($k, $v) = each %t) {
|
||||||
|
$k =~ s/^gitolite-options\.refex-expr\.//;
|
||||||
|
$rules{$k} = $v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
1;
|
56
src/lib/Gitolite/Triggers/RepoUmask.pm
Normal file
56
src/lib/Gitolite/Triggers/RepoUmask.pm
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
package Gitolite::Triggers::RepoUmask;
|
||||||
|
|
||||||
|
use Gitolite::Rc;
|
||||||
|
use Gitolite::Common;
|
||||||
|
use Gitolite::Conf::Load;
|
||||||
|
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
# setting a repo specific umask
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
# this is for people who are too paranoid to trust e.g., gitweb's repo
|
||||||
|
# exclusion logic, but not paranoid enough to put it on a different server
|
||||||
|
|
||||||
|
=for usage
|
||||||
|
|
||||||
|
* In the rc file, add 'RepoUmask::pre_git' and 'RepoUmask::post_create' to
|
||||||
|
the corresponding trigger lists.
|
||||||
|
|
||||||
|
* For each repo that is to get a different umask than the default, add a
|
||||||
|
line like this:
|
||||||
|
|
||||||
|
option umask = 0027
|
||||||
|
|
||||||
|
=cut
|
||||||
|
|
||||||
|
# sadly option/config values are not available at pre_create time for normal
|
||||||
|
# repos. So we have to do a one-time fixup in a post_create trigger.
|
||||||
|
sub post_create {
|
||||||
|
my $repo = $_[1];
|
||||||
|
|
||||||
|
my $umask = option($repo, 'umask');
|
||||||
|
_chdir($rc{GL_REPO_BASE}); # because using option() moves us to ADMIN_BASE!
|
||||||
|
|
||||||
|
return unless $umask;
|
||||||
|
|
||||||
|
# unlike the one in the rc file, this is a string
|
||||||
|
$umask = oct($umask);
|
||||||
|
my $mode = "0" . sprintf("%o", $umask ^ 0777);
|
||||||
|
|
||||||
|
system("chmod -R $mode $repo.git >&2");
|
||||||
|
}
|
||||||
|
|
||||||
|
sub pre_git {
|
||||||
|
my $repo = $_[1];
|
||||||
|
|
||||||
|
my $umask = option($repo, 'umask');
|
||||||
|
_chdir($rc{GL_REPO_BASE}); # because using option() moves us to ADMIN_BASE!
|
||||||
|
|
||||||
|
return unless $umask;
|
||||||
|
|
||||||
|
# unlike the one in the rc file, this is a string
|
||||||
|
umask oct($umask);
|
||||||
|
}
|
||||||
|
|
||||||
|
1;
|
67
src/lib/Gitolite/Triggers/Shell.pm
Normal file
67
src/lib/Gitolite/Triggers/Shell.pm
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
package Gitolite::Triggers::Shell;
|
||||||
|
|
||||||
|
# usage notes: this module must be loaded first in the INPUT trigger list. Or
|
||||||
|
# at least before Mirroring::input anyway.
|
||||||
|
|
||||||
|
# documentation is in the ssh troubleshooting and tips document, under the
|
||||||
|
# section "giving shell access to gitolite users"
|
||||||
|
|
||||||
|
use Gitolite::Rc;
|
||||||
|
use Gitolite::Common;
|
||||||
|
|
||||||
|
# fedora likes to do things that are a little off the beaten track, compared
|
||||||
|
# to typical gitolite usage:
|
||||||
|
# - every user has their own login
|
||||||
|
# - the forced command may not get the username as an argument. If it does
|
||||||
|
# not, the gitolite user name is $USER (the unix user name)
|
||||||
|
# - and finally, if the first argument to the forced command is '-s', and
|
||||||
|
# $SSH_ORIGINAL_COMMAND is empty or runs a non-git/gitolite command, then
|
||||||
|
# the user gets a shell
|
||||||
|
|
||||||
|
sub input {
|
||||||
|
my $shell_allowed = 0;
|
||||||
|
if ( @ARGV and $ARGV[0] eq '-s' ) {
|
||||||
|
shift @ARGV;
|
||||||
|
$shell_allowed++;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ARGV = ( $ENV{USER} ) unless @ARGV;
|
||||||
|
|
||||||
|
return unless $shell_allowed;
|
||||||
|
|
||||||
|
# now determine if this was intended as a shell command or git/gitolite
|
||||||
|
# command
|
||||||
|
|
||||||
|
my $soc = $ENV{SSH_ORIGINAL_COMMAND};
|
||||||
|
|
||||||
|
# no command, just 'ssh alice@host'; doesn't return ('exec's out)
|
||||||
|
shell_out() if $shell_allowed and not $soc;
|
||||||
|
|
||||||
|
return if git_gitolite_command($soc);
|
||||||
|
|
||||||
|
gl_log( 'shell', $ENV{SHELL}, "-c", $soc );
|
||||||
|
exec $ENV{SHELL}, "-c", $soc;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub shell_out {
|
||||||
|
my $shell = $ENV{SHELL};
|
||||||
|
$shell =~ s/.*\//-/; # change "/bin/bash" to "-bash"
|
||||||
|
gl_log( 'shell', $shell );
|
||||||
|
exec { $ENV{SHELL} } $shell;
|
||||||
|
}
|
||||||
|
|
||||||
|
# some duplication with gitolite-shell, factor it out later, if it works fine
|
||||||
|
# for fedora and they like it.
|
||||||
|
sub git_gitolite_command {
|
||||||
|
my $soc = shift;
|
||||||
|
|
||||||
|
my $git_commands = "git-upload-pack|git-receive-pack|git-upload-archive";
|
||||||
|
return 1 if $soc =~ /^($git_commands) /;
|
||||||
|
|
||||||
|
my @words = split ' ', $soc;
|
||||||
|
return 1 if $rc{COMMANDS}{ $words[0] };
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
1;
|
17
src/lib/Gitolite/Triggers/Writable.pm
Normal file
17
src/lib/Gitolite/Triggers/Writable.pm
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
package Gitolite::Triggers::Writable;
|
||||||
|
|
||||||
|
use Gitolite::Rc;
|
||||||
|
use Gitolite::Common;
|
||||||
|
|
||||||
|
sub access_1 {
|
||||||
|
my ( $repo, $aa, $result ) = @_[ 1, 3, 5 ];
|
||||||
|
return if $aa eq 'R' or $result =~ /DENIED/;
|
||||||
|
|
||||||
|
for my $f ( "$ENV{HOME}/.gitolite.down", "$rc{GL_REPO_BASE}/$repo.git/.gitolite.down" ) {
|
||||||
|
next unless -f $f;
|
||||||
|
_die slurp($f) if -s $f;
|
||||||
|
_die "sorry, writes are currently disabled (no more info available)\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
1;
|
100
src/sshkeys-lint
100
src/sshkeys-lint
|
@ -1,100 +0,0 @@
|
||||||
#!/usr/bin/perl -w
|
|
||||||
|
|
||||||
use strict;
|
|
||||||
our (%users, %linenos);
|
|
||||||
|
|
||||||
&usage unless $ARGV[0] and -f $ARGV[0];
|
|
||||||
my @authlines = &filelines($ARGV[0]);
|
|
||||||
my $lineno = 0;
|
|
||||||
for (@authlines)
|
|
||||||
{
|
|
||||||
$lineno++;
|
|
||||||
if (/^# gitolite start/ .. /^# gitolite end/) {
|
|
||||||
warn "line $lineno: non-gitolite key found in gitolite section" if /ssh-rsa|ssh-dss/ and not /command=.*gl-auth-command/;
|
|
||||||
} else {
|
|
||||||
warn "line $lineno: gitolite key found outside gitolite section" if /command=.*gl-auth-command/;
|
|
||||||
}
|
|
||||||
next if /\# gitolite (start|end)/;
|
|
||||||
die "line $lineno: unrecognised line\n" unless /^(?:command=".*gl-auth-command (\S+?)"\S+ )?(?:ssh-rsa|ssh-dss) (\S+)/;
|
|
||||||
my ($user, $key) = ($1 || '', $2);
|
|
||||||
if ($linenos{$key}) {
|
|
||||||
warn "authkeys file line $lineno is repeat of line $linenos{$key}, will be ignored by server sshd\n";
|
|
||||||
next;
|
|
||||||
}
|
|
||||||
$linenos{$key} = $lineno;
|
|
||||||
$users{$key} = ($user ? "maps to gitolite user $user" : "gets you a command line");
|
|
||||||
}
|
|
||||||
|
|
||||||
print "\n";
|
|
||||||
|
|
||||||
# all *.pub in current dir should be exactly one line, starting with ssh-rsa
|
|
||||||
# or ssh-dss
|
|
||||||
|
|
||||||
my @pubkeys = glob("*.pub");
|
|
||||||
die "no *.pub files here\n" unless @pubkeys;
|
|
||||||
for my $pub (@pubkeys) {
|
|
||||||
my @lines = &filelines($pub);
|
|
||||||
die "$pub has more than one line\n" if @lines > 1;
|
|
||||||
die "$pub does not start with ssh-rsa or ssh-dss\n" unless $lines[0] =~ /^(?:ssh-rsa|ssh-dss) (\S+)/;
|
|
||||||
my $key = $1;
|
|
||||||
if ($users{$key}) {
|
|
||||||
print "$pub $users{$key}\n";
|
|
||||||
} else {
|
|
||||||
print "$pub has NO ACCESS to the server\n";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
print <<INFO;
|
|
||||||
|
|
||||||
Git operations using a pubkey that gets you a command line will BYPASS
|
|
||||||
gitolite completely. This means:
|
|
||||||
|
|
||||||
- using "git clone git\@server:reponame" will get you the "does not appear to
|
|
||||||
be a git repository" message
|
|
||||||
- using "git clone git\@server:repositories/reponame" [assuming default value
|
|
||||||
of \$REPO_BASE) will work but subsequent push will fail
|
|
||||||
|
|
||||||
----
|
|
||||||
|
|
||||||
Now you know what pubkey gets you what access.
|
|
||||||
|
|
||||||
To see what key is *actually* being used when you run your commands, try "ssh
|
|
||||||
-v git\@server" or "ssh -v gitolite", and look for a line saying "Offering
|
|
||||||
public key". If there are more than one such lines, the last one is what
|
|
||||||
counts.
|
|
||||||
|
|
||||||
If at any time you are asked for a password (password, not passphrase; see
|
|
||||||
doc/6 for the difference, if needed), then none of this applies anyway.
|
|
||||||
|
|
||||||
INFO
|
|
||||||
|
|
||||||
sub filelines
|
|
||||||
{
|
|
||||||
my $f;
|
|
||||||
my $fn = shift;
|
|
||||||
open ($f, "<", $fn) or die "open $fn failed: $!\n";
|
|
||||||
return <$f>;
|
|
||||||
}
|
|
||||||
|
|
||||||
sub usage
|
|
||||||
{
|
|
||||||
print STDERR <<EOF;
|
|
||||||
|
|
||||||
On your *client*:
|
|
||||||
|
|
||||||
- copy the server's ~/.ssh/authorized_keys file to your *client*'s
|
|
||||||
/tmp/foo (maybe using "scp" or whatever)
|
|
||||||
|
|
||||||
- cd to the ~/.ssh directory (which contains all the pub keys this client
|
|
||||||
can use)
|
|
||||||
|
|
||||||
- run "$0 /tmp/foo"
|
|
||||||
|
|
||||||
Note: people who have so many keypairs they keep them in *sub*-directories of
|
|
||||||
~/.ssh [you know who you are ;-)] can figure it out themselves; you clearly
|
|
||||||
know enough about ssh not to need my help!
|
|
||||||
|
|
||||||
EOF
|
|
||||||
|
|
||||||
exit 1;
|
|
||||||
}
|
|
34
src/syntactic-sugar/continuation-lines
Normal file
34
src/syntactic-sugar/continuation-lines
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
# vim: syn=perl:
|
||||||
|
|
||||||
|
# "sugar script" (syntactic sugar helper) for gitolite3
|
||||||
|
|
||||||
|
# Enabling this script in the rc file allows you to use back-slash escaped
|
||||||
|
# continuation lines, like in C or shell etc.
|
||||||
|
|
||||||
|
# This script also serves as an example "sugar script" if you want to write
|
||||||
|
# your own (and maybe send them to me). A "sugar script" in gitolite will be
|
||||||
|
# executed via a perl 'do' and is expected to contain one function called
|
||||||
|
# 'sugar_script'. This function should take a listref and return a listref.
|
||||||
|
# Each item in the list is one line. There are NO newlines; g3 kills them off
|
||||||
|
# fairly early in the process.
|
||||||
|
|
||||||
|
# If you're not familiar with perl please do not try this. Ask me to write
|
||||||
|
# you a sugar script instead.
|
||||||
|
|
||||||
|
sub sugar_script {
|
||||||
|
my $lines = shift;
|
||||||
|
|
||||||
|
my @out = ();
|
||||||
|
my $keep = '';
|
||||||
|
for my $l (@$lines) {
|
||||||
|
if ( $l =~ s/\\$// ) {
|
||||||
|
$keep .= $l;
|
||||||
|
} else {
|
||||||
|
$l = $keep . $l if $keep;
|
||||||
|
$keep = '';
|
||||||
|
push @out, $l;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return \@out;
|
||||||
|
}
|
32
src/syntactic-sugar/keysubdirs-as-groups
Normal file
32
src/syntactic-sugar/keysubdirs-as-groups
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
# vim: syn=perl:
|
||||||
|
|
||||||
|
# "sugar script" (syntactic sugar helper) for gitolite3
|
||||||
|
|
||||||
|
# Enabling this script in the rc file allows you to use subdirectories in
|
||||||
|
# keydir as group names. The last component other than keydir itself will be
|
||||||
|
# taken as the group name.
|
||||||
|
|
||||||
|
sub sugar_script {
|
||||||
|
Gitolite::Common::trace( 2, "running 'keysubdirs-as-groups' sugar script..." );
|
||||||
|
my $lines = shift;
|
||||||
|
|
||||||
|
my @out = @{$lines};
|
||||||
|
unshift @out, groupnames();
|
||||||
|
|
||||||
|
return \@out;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub groupnames {
|
||||||
|
my @out = ();
|
||||||
|
my %members = ();
|
||||||
|
for my $pk (`find ../keydir/ -name "*.pub"`) {
|
||||||
|
next unless $pk =~ m(.*/([^/]+)/([^/]+?)(?:@[^./]+)?\.pub$);
|
||||||
|
next if $1 eq 'keydir';
|
||||||
|
$members{$1} .= " $2";
|
||||||
|
}
|
||||||
|
for my $m ( sort keys %members ) {
|
||||||
|
push @out, "\@$m =" . $members{$m};
|
||||||
|
}
|
||||||
|
|
||||||
|
return @out;
|
||||||
|
}
|
74
src/syntactic-sugar/macros
Normal file
74
src/syntactic-sugar/macros
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
# vim: syn=perl:
|
||||||
|
|
||||||
|
# "sugar script" (syntactic sugar helper) for gitolite3
|
||||||
|
|
||||||
|
# simple line-wise macro processor
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
# see documentation at the end of this script
|
||||||
|
|
||||||
|
my %macro;
|
||||||
|
sub sugar_script {
|
||||||
|
my $lines = shift;
|
||||||
|
my @out = ();
|
||||||
|
|
||||||
|
my $l = join("\n", @$lines);
|
||||||
|
while ($l =~ s/^macro (\w+)\b(.*?)\nend//ms) {
|
||||||
|
$macro{$1} = $2;
|
||||||
|
}
|
||||||
|
|
||||||
|
$l =~ s/^((\w+)\b.*)/$macro{$2} ? expand($1) : $1/gem;
|
||||||
|
|
||||||
|
$lines = [split "\n", $l];
|
||||||
|
return $lines;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub expand {
|
||||||
|
my $l = shift;
|
||||||
|
my ($word, @arg) = split ' ', $l;
|
||||||
|
my $v = $macro{$word};
|
||||||
|
$v =~ s/%(\d+)/$arg[$1-1] or die "macro '$word' needs $1 arguments at '$l'\n"/gem;
|
||||||
|
return $v;
|
||||||
|
}
|
||||||
|
|
||||||
|
__END__
|
||||||
|
|
||||||
|
Documentation is mostly by example.
|
||||||
|
|
||||||
|
Setup:
|
||||||
|
|
||||||
|
* the line
|
||||||
|
'macros',
|
||||||
|
should be added to the SYNTACTIC_SUGAR list in ~/.gitolite.rc
|
||||||
|
|
||||||
|
Notes on macro definition:
|
||||||
|
|
||||||
|
* the keywords 'macro' and 'end' should start on a new line
|
||||||
|
* the first word after 'macro' is the name of the macro, and the rest, until
|
||||||
|
the 'end', is the body
|
||||||
|
|
||||||
|
Notes on macro use:
|
||||||
|
|
||||||
|
* the macro name should be the first word on a line
|
||||||
|
* the rest of the line is used as arguments to the macro
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
if your conf contains:
|
||||||
|
|
||||||
|
macro foo repo aa-%1
|
||||||
|
RW = u1 %2
|
||||||
|
R = u2
|
||||||
|
end
|
||||||
|
|
||||||
|
foo 1 alice
|
||||||
|
foo 2 bob
|
||||||
|
|
||||||
|
this will effectively turn into
|
||||||
|
|
||||||
|
repo aa-1
|
||||||
|
RW = u1 alice
|
||||||
|
R = u2
|
||||||
|
|
||||||
|
repo aa-2
|
||||||
|
RW = u1 bob
|
||||||
|
R = u2
|
17
src/triggers/bg
Executable file
17
src/triggers/bg
Executable file
|
@ -0,0 +1,17 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# quick and dirty program to background any of the triggers programs that are
|
||||||
|
# taking too long. To use, just replace a line like
|
||||||
|
# 'post-compile/update-gitweb-access-list',
|
||||||
|
# with
|
||||||
|
# 'bg post-compile/update-gitweb-access-list',
|
||||||
|
|
||||||
|
# We dump output to a file in the log directory but please keep in mind this
|
||||||
|
# is not a "log" so much as a redirection of the entire output.
|
||||||
|
|
||||||
|
echo `date` $GL_TID "$0: $@" >> $GL_LOGFILE.bg
|
||||||
|
|
||||||
|
path=${0%/*}
|
||||||
|
script=$path/$1; shift
|
||||||
|
|
||||||
|
( ( $script "$@" < /dev/null >> $GL_LOGFILE.bg 2>&1 & ) )
|
35
src/triggers/partial-copy
Executable file
35
src/triggers/partial-copy
Executable file
|
@ -0,0 +1,35 @@
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# this is a wee bit expensive in terms of forks etc., compared to doing it in
|
||||||
|
# perl, but I wanted to show how *easy* it actually is now. And really,
|
||||||
|
# you'll only notice if you access this repo like a hundred times a minute or
|
||||||
|
# something so don't sweat it.
|
||||||
|
|
||||||
|
# given a repo and a user, check if option('partialCopyOf') is set, and if so,
|
||||||
|
# fetch all allowed branches from there.
|
||||||
|
|
||||||
|
die() { echo "$@" >&2; exit 1; }
|
||||||
|
|
||||||
|
# make sure we're being called from the pre_git trigger
|
||||||
|
[ "$1" = "PRE_GIT" ] || die I must be called from PRE_GIT, not "$1"
|
||||||
|
shift
|
||||||
|
|
||||||
|
repo=$1
|
||||||
|
user=$2
|
||||||
|
main=`git config --file $GL_REPO_BASE/$repo.git/config --get gitolite.partialCopyOf`;
|
||||||
|
[ -z "$main" ] && exit 0
|
||||||
|
|
||||||
|
# "we", "our repo" => the partial copy
|
||||||
|
# "main", "pco" => the one which we are a "partial copy of"
|
||||||
|
|
||||||
|
cd $GL_REPO_BASE/$main.git
|
||||||
|
|
||||||
|
for ref in `git for-each-ref refs/heads '--format=%(refname)'`
|
||||||
|
do
|
||||||
|
cd $GL_REPO_BASE/$repo.git
|
||||||
|
|
||||||
|
gitolite access -q $repo $user R $ref &&
|
||||||
|
git fetch -f $GL_REPO_BASE/$main.git $ref:$ref
|
||||||
|
done
|
||||||
|
|
||||||
|
exit 0
|
146
src/triggers/post-compile/ssh-authkeys
Executable file
146
src/triggers/post-compile/ssh-authkeys
Executable file
|
@ -0,0 +1,146 @@
|
||||||
|
#!/usr/bin/perl
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
use File::Temp qw(tempfile);
|
||||||
|
use Getopt::Long;
|
||||||
|
|
||||||
|
use lib $ENV{GL_LIBDIR};
|
||||||
|
use Gitolite::Rc;
|
||||||
|
use Gitolite::Common;
|
||||||
|
|
||||||
|
$|++;
|
||||||
|
|
||||||
|
# best called via 'gitolite trigger POST_COMPILE'; other modes at your own
|
||||||
|
# risk, especially if the rc file specifies arguments for it. (That is also
|
||||||
|
# why it doesn't respond to "-h" like most gitolite commands do).
|
||||||
|
|
||||||
|
# option procesing
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
# currently has one option:
|
||||||
|
# -kfn, --key-file-name adds the keyfilename as a second argument
|
||||||
|
|
||||||
|
my $kfn = '';
|
||||||
|
GetOptions( 'key-file-name|kfn' => \$kfn, );
|
||||||
|
|
||||||
|
tsh_try("sestatus");
|
||||||
|
my $selinux = ( tsh_text() =~ /enabled/ );
|
||||||
|
|
||||||
|
my $ab = $rc{GL_ADMIN_BASE};
|
||||||
|
trace( 2, "'keydir' not found in '$ab'; exiting" ), exit if not -d "$ab/keydir";
|
||||||
|
my $akdir = "$ENV{HOME}/.ssh";
|
||||||
|
my $akfile = "$ENV{HOME}/.ssh/authorized_keys";
|
||||||
|
my $glshell = $rc{GL_BINDIR} . "/gitolite-shell";
|
||||||
|
my $auth_options = auth_options();
|
||||||
|
|
||||||
|
sanity();
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
_chdir($ab);
|
||||||
|
|
||||||
|
# old data
|
||||||
|
my $old_ak = slurp($akfile);
|
||||||
|
my @non_gl = grep { not /^# gito.*start/ .. /^# gito.*end/ } slurp($akfile);
|
||||||
|
chomp(@non_gl);
|
||||||
|
my %seen = map { $_ => 'a non-gitolite key' } ( fp(@non_gl) );
|
||||||
|
|
||||||
|
# pubkey files
|
||||||
|
chomp( my @pubkeys = `find keydir/ -type f -name "*.pub" | sort` );
|
||||||
|
my @gl_keys = ();
|
||||||
|
for my $f (@pubkeys) {
|
||||||
|
my $fp = fp($f);
|
||||||
|
if ( $seen{$fp} ) {
|
||||||
|
_warn "$f duplicates $seen{$fp}, sshd will ignore it";
|
||||||
|
} else {
|
||||||
|
$seen{$fp} = $f;
|
||||||
|
}
|
||||||
|
push @gl_keys, grep { /./ } optionise($f);
|
||||||
|
}
|
||||||
|
|
||||||
|
# dump it out
|
||||||
|
if (@gl_keys) {
|
||||||
|
my $out = join( "\n", @non_gl, "# gitolite start", @gl_keys, "# gitolite end" ) . "\n";
|
||||||
|
|
||||||
|
my $ak = slurp($akfile);
|
||||||
|
_die "'$akfile' changed between start and end of this program!" if $ak ne $old_ak;
|
||||||
|
_print( $akfile, $out );
|
||||||
|
}
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
sub sanity {
|
||||||
|
_die "'$glshell' not found; this should NOT happen..." if not -f $glshell;
|
||||||
|
_die "'$glshell' found but not readable; this should NOT happen..." if not -r $glshell;
|
||||||
|
_die "'$glshell' found but not executable; this should NOT happen..." if not -x $glshell;
|
||||||
|
|
||||||
|
_warn "$akdir missing; creating a new one" if not -d $akdir;
|
||||||
|
_warn "$akfile missing; creating a new one" if not -f $akfile;
|
||||||
|
|
||||||
|
_mkdir( $akdir, 0700 ) if not -d $akfile;
|
||||||
|
if ( not -f $akfile ) {
|
||||||
|
_print( $akfile, "" );
|
||||||
|
chmod 0700, $akfile;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub auth_options {
|
||||||
|
my $auth_options = $rc{AUTH_OPTIONS};
|
||||||
|
$auth_options ||= "no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty";
|
||||||
|
|
||||||
|
return $auth_options;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub fp {
|
||||||
|
# input: see below
|
||||||
|
# output: a (list of) FPs
|
||||||
|
my $in = shift || '';
|
||||||
|
if ( $in =~ /\.pub$/ ) {
|
||||||
|
# single pubkey file
|
||||||
|
_die "bad pubkey file '$in'" unless $in =~ $REPONAME_PATT;
|
||||||
|
return fp_file($in);
|
||||||
|
} elsif ( -f $in ) {
|
||||||
|
# an authkeys file
|
||||||
|
return map { fp_line($_) } grep { !/^#/ and /\S/ } slurp($in);
|
||||||
|
} else {
|
||||||
|
# one or more actual keys
|
||||||
|
return map { fp_line($_) } grep { !/^#/ and /\S/ } ( $in, @_ );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub fp_file {
|
||||||
|
return $selinux++ if $selinux; # return a unique "fingerprint" to prevent noise
|
||||||
|
my $f = shift;
|
||||||
|
my $fp = `ssh-keygen -l -f '$f'`;
|
||||||
|
chomp($fp);
|
||||||
|
_die "fingerprinting failed for '$f'" unless $fp =~ /([0-9a-f][0-9a-f](:[0-9a-f][0-9a-f])+)/;
|
||||||
|
$fp = $1;
|
||||||
|
return $fp;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub fp_line {
|
||||||
|
my ( $fh, $fn ) = tempfile();
|
||||||
|
print $fh shift;
|
||||||
|
close $fh;
|
||||||
|
my $fp = fp_file($fn);
|
||||||
|
unlink $fn;
|
||||||
|
return $fp;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub optionise {
|
||||||
|
my $f = shift;
|
||||||
|
|
||||||
|
my $user = $f;
|
||||||
|
$user =~ s(.*/)(); # foo/bar/baz.pub -> baz.pub
|
||||||
|
$user =~ s/(\@[^.]+)?\.pub$//; # baz.pub, baz@home.pub -> baz
|
||||||
|
|
||||||
|
my @line = slurp($f);
|
||||||
|
if ( @line != 1 ) {
|
||||||
|
_warn "$f does not contain exactly 1 line; ignoring";
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
chomp(@line);
|
||||||
|
return "command=\"$glshell $user" . ( $kfn ? " $f" : "" ) . "\",$auth_options $line[0]";
|
||||||
|
}
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue