Compare commits

...

199 Commits

Author SHA1 Message Date
Denis Knauf a6f6886e84 README.txt -> README.md + clean up 2013-01-02 14:55:04 +01:00
Sitaram Chamarty 089f0f9d9e on removing a repo...
Not following through on instructions to remove a repo, per [1], is not
sufficient.  Even if you did just the first step, the repo should  no
longer be accessible.  See [2] for discussion.

As a bonus, we get rid of one pesky warning that always confused people.
(In hindsight -- this confusion itself should have been a warning that
something is wrong and needed fixing!)

[1]: http://sitaramc.github.com/gitolite/repos.html
[2]: http://groups.google.com/group/gitolite/browse_thread/thread/a3d4c3e917056abb
2012-12-31 06:23:28 +05:30
Sitaram Chamarty 5aef1adc7b list-dangling-repos: are we there yet?
<sigh>First I forgot @groups that may contain repos and patterns, then I
forgot patterns where the CREATOR token is used (this is the fix here).
2012-12-31 05:48:18 +05:30
Sitaram Chamarty 1fefb1c0d9 v3.3 2012-12-29 13:58:12 +05:30
Sitaram Chamarty ea3d04ea0a perms batch mode confuses; print something to help
What happens is that running

    ssh git@host perms reponame

appears to hang, since it is waiting for STDIN.  I added a message to
help, since we don't want users losing files accidentally!

(The other alternative is to add a specific option for batch mode, but
this is backward incompatible for people who have scripts that may be
doing this).

thanks to Caleb Cushing for catching this

----

The "make sure Ctrl-C gets caught" thing needs some explanation.

Without it, a user could inadvertently lose his gl-perms file if he ran
the command in batch mode.  You'd think that the Ctrl-C would hit the

    for (<>) {

line and bail, but it manages to reach the

    _print( $pf, @a );

line somehow.  Even trapping SIG INT does not help.

I suspect it is to do with how signals are propagated by ssh across a
"no-pty" session, but am not sure.
2012-12-29 13:40:13 +05:30
Sitaram Chamarty 84424e48b9 bug fix: perms propagation to slaves...
Sometime after v3.2, I fixed what looked like an information disclosure
issue, where a user could determine if an arbitrary repo existed or not,
even if he had no rights to see the repo.  This was:

    96cc2ea "new features relating to creating wild repos:"

Unfortunately, this appears to have broken gl-perms propagation to
slaves, because now running "perm -c" on an existing repo dies!

If you run

    git diff 96cc2ea^ <this commit> -- src/commands/perms

you'll see how simple the fix *should* have been :-(
2012-12-29 13:40:13 +05:30
Sitaram Chamarty b303694882 minor bugly...
please remember we make up words here, like refex was a word we created
to mean "a regex that matches a ref".

A "bugly", then, is a bug that's merely ugly (and not a real problem!)
2012-12-29 13:40:13 +05:30
Sitaram Chamarty b9bbb78278 D: allow rm and unlock to be disabled 2012-12-19 07:19:50 +05:30
Sitaram Chamarty 3513f4a153 fix bug in list-dangling-repos
Still, 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!
2012-12-19 06:31:11 +05:30
Sitaram Chamarty 4f4658274d CREATOR need only be a "word" in wild repo patterns
this was a v2 compat breakage, caught by Dominik Schäfer
(schaedpq at gmail)
2012-12-19 06:27:24 +05:30
Sitaram Chamarty 2048484578 add more detail to error message
this error normally happens due to some permission issue on the log
file, but we weren't printing the actual cause, so it was confusing
2012-12-14 07:58:14 +05:30
Sitaram Chamarty 8e3ee2f9c1 (minor) macro buglets
- allow parameter-less macros
  - allow macro body to start on next line
2012-12-14 07:58:14 +05:30
Sitaram Chamarty 3103d68a75 new trigger: update-gitweb-daemon-from-options
another way to update gitweb and daemon access lists
2012-12-14 07:58:14 +05:30
Sven Strickroth f89408adb1 Set Content-Type to text/plain for gitolite commands over http
Explicitly set "Content-Type: text/plain" for gitolite commands when
issued over http, so that it is possible to see the output with normal
browsers.

(At least) Apache httpd might set the Content-Type to something
different and triggers a download instead of showing the text directly.

Signed-off-by: Sven Strickroth <email@cs-ware.de>
2012-12-09 08:09:02 +05:30
Sitaram Chamarty fc7ddfc818 (minor) lint had syntax errors
thanks to xcat on #gitolite for catching it
(shows you how often it gets used I guess!)
2012-12-07 17:30:56 +05:30
Sitaram Chamarty f1c69a3ec0 bugfix: don't delete description file when running perms
thanks to drue on #gitolite for catching it
2012-12-05 06:00:00 +05:30
Sitaram Chamarty 2741fadc9d a few minor changes
* minor typos
  * perltidy on Tsh
  * a minor optimisation to "do" in gl-conf
  * remove inapplicable caveat in fork command
2012-12-04 05:43:48 +05:30
Sitaram Chamarty b6d6260dbb prevent empty %groups being created in compiled conf
this would happen if @all was used but no actual groups were defined,
and would in turn cause a parse error on the compiled conf because it
now ends with a 'false'.

thanks to Jelle Raaijmakers
2012-11-28 06:22:55 +05:30
Sitaram Chamarty 72e36f32aa oops; hashes were getting printed twice in certain cases...
harmless but wasteful
2012-11-28 05:49:48 +05:30
Stephen Palmer d2214b06b5 Fixed bug in lock script
the unlock command was not checking the correct hash key to match
the user name
2012-11-27 08:09:46 +05:30
Sitaram Chamarty 96cc2eaf41 new features relating to creating wild repos:
- new 'create' command for explicit creation
  - new 'AutoCreate' trigger to prevent auto-creation on read operations
    or both read and write operations
  - a few related fixups to the perms command
2012-11-22 20:50:20 +05:30
Sebastian Koslowski 96be9503ef sudo command: CLI fix: 2 non-empty args required 2012-11-22 20:50:20 +05:30
Sitaram Chamarty 7cec71b0ef minor fixups to some non-core programs
(following a bit of a doc shakeup)
2012-11-22 15:59:48 +05:30
Sitaram Chamarty cd838411fa 'gitolite mirror' needs to set exit code on push failure 2012-11-21 21:16:01 +05:30
Sitaram Chamarty 2018267a45 (minor) fixes to lint program, mainly usage message 2012-11-21 19:55:12 +05:30
Sitaram Chamarty a26532d635 allow simple macros in conf file 2012-11-19 07:48:41 +05:30
Sitaram Chamarty 5f9789ed8e v3.2 2012-11-14 15:45:45 +05:30
Sitaram Chamarty d3d93961a0 Uggh; horrible inner loop screwing up all performance :-(
This might actually make the redis version unnecessary for most people!
And if it does, well shame on me for not instrumenting things at a more
granular level before going all "oh we need a cache!"

[In my defense, I blame redis for being such a sweet little tool that I
felt compelled to use it somehow!]

----

t/sequence failed because the test itself was in error; fixed.
2012-11-14 15:43:57 +05:30
Sitaram Chamarty 1f96180df0 allow multi-line pubkeys; see code for doc 2012-11-13 08:45:45 +05:30
gitolite tester 57760d7e1b refex-expr: die when admin forgets to add the required line to the rc 2012-11-13 08:09:14 +05:30
gitolite tester 16f2d9b879 gl-conf must be created even if the repo para has only config lines
(i.e., no access rules but only config lines)
2012-11-13 07:00:22 +05:30
Sitaram Chamarty c03d107bac help run some trigger programs in the background 2012-11-10 14:21:52 +05:30
Sitaram Chamarty d491b5384f (minor) add quick and dirty timer code to Common.pm 2012-11-09 18:02:16 +05:30
Sitaram Chamarty 8a9564f171 some minor rearrangements of code...
why?  now that would be telling!
2012-11-08 19:12:20 +05:30
Sitaram Chamarty a509b208e3 move %GL_REPO and %GL_CREATOR substitution into core
see usage example at the end of src/triggers/upstream
2012-11-07 05:36:28 +05:30
Sitaram Chamarty be61cd2d66 make sure gl-perms exists, even if it is empty...
I expect this to help if we optimise the rule generation by caching.
2012-11-06 09:15:55 +05:30
Sitaram Chamarty 70ad045e08 (minor fixups to some non-code parts) 2012-10-31 06:24:44 +05:30
Andrew Page 2aa129bc70 fix for keysubdirs-as-groups sugar script to support "old style multi-keys" for users 2012-10-29 17:15:52 -06:00
Sitaram Chamarty a802071a5e (test suite) stop using 'ls' to test for presence/absence of files/directories
another of those "duh!  what was I thinking" moments, this specific one
being "why test that files/directories are created with the right user
and group IDs?  Shouldn't that be out of your control, as well as
totally unnecessary on a sane system?"
2012-10-27 13:20:55 +05:30
Sitaram Chamarty 4eb8cd4ad1 (minor) bash -> sh changes in some non-core code
/bin/bash is muscle memory for me, although it appears that not too much
of the actual code is bash-specific, so it's reasonably easy to fix.
2012-10-27 07:07:30 +05:30
Sitaram Chamarty 3eefc06551 (minor) clarify that D only works on wild repos 2012-10-10 13:43:17 +05:30
Eugene E. Kashpureff Jr 896ada58c0 Fix spurious error in triggers/upstream
The initial fetch of a new repo which has 'upstream' read-only mirroring
configured will cause a spurious error concerning FETCH_HEAD not yet
existing. This silences the error.
2012-10-10 07:59:52 +00:00
Sitaram Chamarty 51ab768e2a v3.1 2012-10-05 14:42:25 +05:30
Sitaram Chamarty f636ce3ba3 (security) fix bug in pattern to detect path traversal
while we're about it, add the same check to some of the internal
routines, so that commands can also be protected.

finally, just to make sure we don't lose it again in some other fashion,
add a few tests for path traversal...
2012-10-05 12:28:20 +05:30
Sitaram Chamarty 0d371ac957 call GROUPLIST_PGM before determining user_roles()...
thanks to Stephane Chazelas [1]

[1]: https://groups.google.com/d/topic/gitolite/gy_ZkrxGSjg
2012-10-04 22:03:19 +05:30
Sitaram Chamarty 2dbaa4d12e (minor) move a small chunk of code out of a loop 2012-09-26 14:58:56 +05:30
Sitaram Chamarty 6328ec2cbe dont auto-vivify empty entries in %repos...
before this, trying to access a wild repo would create an empty hash in
%repos.  This is pretty harmless, but at some later point, memberships()
would try to use that in a pattern, attempting to match the real repo
being access-checked.

Which is still fine if your repo doesn't look like "libstdc++" AND
you're using some recent perl.

However, for perl 5.8.8, and if the repo has a ++ in it, perl barfs.

Here's a test program to check your perl:

    #!/usr/bin/perl

    $base="foo/u1/libstdc++";
    $i="foo/u1/libstdc++";

    if ( $base =~ /^$i$/ ) {
        print 1;
    } else {
        print 2;
    }

On 5.14.2 I get "2".  On 5.8.8 I get:

    Nested quantifiers in regex; marked by <-- HERE in m/^foo/u1/libstdc++ <-- HERE $/ at ./aa.pl line 6.
2012-09-25 19:10:47 +05:30
Sitaram Chamarty 3fe8ecf974 (minor) avoid spurious 'repo missing' messages for repo patterns 2012-09-25 19:05:57 +05:30
Sitaram Chamarty 9606e35528 help cgit folks out a bit :) 2012-09-24 04:53:20 +05:30
Sitaram Chamarty 724c741335 prevent barfage when presetting the rc file 2012-09-20 06:21:44 +05:30
Sitaram Chamarty e59c3ba9f9 (minor docfix) add info on using Easy.pm from elsewhere 2012-09-19 17:47:12 +05:30
Sitaram Chamarty aec8c71890 'help' command barfage fix
should not barf if LOCAL_CODE is defined but it doesn't contain a
"commands" subdirectory.
2012-08-30 18:40:24 +05:30
Sitaram Chamarty ed4862ff96 minor changes to README 2012-08-30 18:39:42 +05:30
Sitaram Chamarty cc9727c42b minor bug in include file handing...
gitolite does indeed try to not load itself twice, but I forgot that by
that time the pwd is ~/.gitolite/conf not ~/.gitolite so it always ended
up reading itself twice in case of a wildcard include.
2012-08-17 22:26:03 +05:30
Sitaram Chamarty 7409635823 (minor) add a 'dd' function to quickly dump stuff to STDERR 2012-08-17 10:04:32 +05:30
Olof Johansson ba67f6f9ca Bailout tests unless envvar $GITOLITE_TEST is 'y'
[committer made some changes to t/README]
2012-08-10 11:31:48 +05:30
Nate Jones 31166e1e1c find symlinked commands when generating help list 2012-08-03 13:48:19 -07:00
Sitaram Chamarty b2a3509e63 point people to mailing list for general questions 2012-07-27 21:19:07 +05:30
Patrick Westerhoff 57bea39a1e Add special %GL_CREATOR variable for git-config
Add a special variable `%GL_CREATOR` to the the git-config trigger that
is replaced by the name of the repository creator (if any).

This can be useful to set up the default owner configuration for wild
repositories.

Example:

    repo assignments/CREATOR/a[0-9][0-9]
        C   = @students
        RW+ = CREATOR
        config gitweb.owner = %GL_CREATOR

----

committer added an if condition to the s/// line.
2012-07-19 16:16:22 +05:30
Sitaram Chamarty f4eb6dcb53 'rsync' command to create and send bundles (manual smoke test only)
run 'ssh git@host rsync -h' for usage, as usual
2012-07-19 14:40:41 +05:30
Sitaram Chamarty 8ad1eee220 migrated 'who-pushed' command (manual smoke test only) 2012-07-17 16:52:56 +05:30
Dave Abrahams d3279e4ad0 Fix a typo 2012-07-12 18:06:21 +05:30
Sitaram Chamarty fd0778e6d6 (minor) don't keep adding the same thing to $PATH 2012-07-10 21:10:06 +05:30
Sitaram Chamarty f35db87efc (minor) new mailing list 2012-07-10 20:59:36 +05:30
Sitaram Chamarty f545bc08f6 minor fixups 2012-07-03 08:06:59 +05:30
Sitaram Chamarty db2cf23379 logical expressions on refexes :-) 2012-06-29 22:19:06 +05:30
Sitaram Chamarty af437c3a7b v3.04 2012-06-27 07:10:09 +05:30
Sitaram Chamarty 49580fe4b3 doc split :(
Background

  * I needed to have the documentation under CC-BY-NC-SA (something
    happened to force me to choose)

  * Distros don't like the NC part.  They'd rather drop the
    documentation entirely instead

  * I don't like that; it bothers me that even a clueful guy won't be
    able to do a basic install with what comes in the package.

  * Meanwhile, I have always had the occasional "TL;DR" complaint about
    my docs

Taking all this into account, it seemed like the best way was:

  * Create a brand new README.txt that is crisp enough for someone to
    glance through and quickly get started.  At more then 300 lines, it
    covers enough ground that probably 60% of sites don't need more.

    Put this under the CC-BY-SA license, which is on the "good" list for
    Fedora (and also Debian, I am told).

  * Move the current documents to a new "gitolite-doc" repo that distros
    can simply ignore, but anyone who has trouble can go to.

    Make sure the online pages have the same content at the same URLs as
    they do now, getting it instead from this new repo.

    Link to the main URL in the new README.txt
2012-06-26 20:58:51 +05:30
Sitaram Chamarty 53f9a867df accumulated docfixes...
- non-core documentation reduced to be easier to maintain
  - much reduced progit section submitted to scott chacon, necessitating
    some changes to this copy
  - other minor stuff
  - the "idiot-proof setup" :)

(plus get rid of that silly "dot.pl"; it's not needed any more, if it
ever was!)
2012-06-25 12:17:00 +05:30
Sitaram Chamarty 53543ee3e6 partial-copy would not propagate deletes; fixed 2012-06-23 09:11:08 +05:30
Konstantin Gribov fb9829a698 Fixed url decoding in http gitolite command bypass.
Only '+' sign was unescaped in `http_simulate_ssh_connection()`.
When user translates `ssh git@host perms <repo> + <role> <user>` to
`curl https://host/git/perms?<repo>+%2b+<role>+<user>` nothing happens.
This commit fixes it modifying url unescaping.

committer notes: changed the regex per 'man URI::Escape'
2012-06-22 11:49:28 +05:30
Sitaram Chamarty 621815611c (duh!) report rc file syntax errors
the "duh!" is because I should have done this long ago...
2012-06-21 06:49:22 +05:30
Sitaram Chamarty a454111d32 repo-specific umask
manually smoke tested but should be fine
2012-06-21 05:31:15 +05:30
Frode Rystad 858f13cf31 Added information about install destinations supported by SELinux to troubleshooting guide 2012-06-20 08:59:42 +02:00
Sitaram Chamarty c9d5a13194 help command learns to deal with LOCAL_CODE 2012-06-19 21:00:53 +05:30
Sitaram Chamarty 7dcb857347 (accumulated docfixes) 2012-06-19 12:13:00 +05:30
Sitaram Chamarty 56d11deb55 (minor) one more 'internal' message bites the dust 2012-06-19 12:11:27 +05:30
Sitaram Chamarty a171053ab3 (minor) improve SNR of some error messages :) 2012-06-17 09:31:38 +05:30
Sitaram Chamarty bcef2be640 (minor) usage message oops in 'install -h' 2012-06-16 23:30:32 +05:30
Sitaram Chamarty fbd745958e PRE_ and POST_CREATE triggers get an extra argument...
...when invoked by single-repo operations like auto-creating a wild
repo, or running perms or fork.
2012-06-14 19:22:12 +05:30
Sitaram Chamarty 57f82ee044 new 'list-dangling-repos' command 2012-06-14 19:22:12 +05:30
Sitaram Chamarty 4373c5c74c GL_BINDIR2 becomes LOCAL_CODE, allows hook propagation also...
plus a bunch of doc changes
2012-06-14 19:22:12 +05:30
Sitaram Chamarty 3c0f177481 Allow user-specified programs to override system-installed ones
(manually tested)

  - new rc var: GL_BINDIR2; see doc update in this commit

  - added _which() function to search both $GL_BINDIR and $GL_BINDIR2
  - 'gitolite <command>', non-perl triggers, VREFs, and sugar, use this

  - unshifted $GL_BINDIR2/lib into @INC upfront in Rc.pm
  - perl triggers use this
2012-06-09 08:25:51 +05:30
Sitaram Chamarty cd37fe7c36 (test suite) changes in test suite due to upgrading to F17
- git version bumps up, causes minor change in t/merge-check.t
  - for some strange reason apache cannot see /tmp/gitolite-http-authuserfile
    (I thought private /tmp was only if you enabled selinux...)
2012-06-08 17:08:15 +05:30
Randal L. Schwartz 8e15d3a510 gitolite patch to enable keydir to be a symlink 2012-06-08 03:47:04 +05:30
milki 1f6a17c155 fix D perm reference 2012-06-06 12:21:46 -07:00
Sitaram Chamarty 5300809103 ACCESS_2 gets 2 more arguments, and gets called for each VREF 2012-06-06 20:33:59 +05:30
Sitaram Chamarty 10cd5b9abe 'upstream' trigger can now be used as a server command also
i.e., a client fetch will invoke the push, (subject to 'nice' setting),
but you can also force a fetch regardless of last fetch time by running
this command directly on the server:

    gitolite ../triggers/upstream fetch <reponame>

Also, use FETCH_HEAD instead of own sentinel file (idea courtesy Luke Lu)
2012-06-06 19:28:18 +05:30
Sitaram Chamarty 8b78dee18c 'upstream' -- script to maintain local copies of external repos
instructions and notes in the source
2012-06-05 22:23:23 +05:30
Sitaram Chamarty f59ad8cafc (accumulated docfixes) esp a large section on the INPUT trigger 2012-06-05 11:39:18 +05:30
Sitaram Chamarty 17c41ce63b new 'sudo' command 2012-06-03 14:20:27 +05:30
Sitaram Chamarty ad34cf2856 minor backward incompat breakage in 'gitolite query-rc'
'gitolite query-rc' now only queries one variable at a time.  That is,
you cannot do something like this:

    gitolite query-rc UMASK GL_ADMIN_BASE

to query both variables.  I think this is rarely used, plus it is easy
to work-around (just run two separate commands), so it was sacrificed
for the ability to do this:

    gitolite query-rc -q COMMANDS fork

which tells you whether $rc{COMMANDS}{fork} exists or not.
2012-06-03 13:00:38 +05:30
John Keeping 4abadc2b54 Grant shell access to all keys for shell users
If a user has multiple keys, ssh-authkeys-shell-users will only add the
"-s" flag to the first key it finds.  Change the substitution to apply
to all matching lines and hence grant shell access to all of the user's
keys.

Signed-off-by: John Keeping <john@keeping.me.uk>
2012-06-03 13:00:38 +05:30
Sitaram Chamarty db70595b87 fixup to pushing wild repos permissions...
- fix docs to explicitly state that mirroring wild repos is a bad idea
    if the authentication data is not the same on the peers.

  - an important check against a malicious peer was missed out.  If
    people heed the warning above this check is not really needed but it
    is good for completeness.

  - warning about redirected pushes removed, thanks to Ronald Ip having
    tested it and reported it working.
2012-06-03 07:44:08 +05:30
Sitaram Chamarty 78866f6f28 (experimental code) push wild repos across a master slave connection
It creates the repo on the remote side (getting the creator name from
the gl-creator file and sending it across), as well as sending gl-perms
on subsequent connections.

This has only been minimally tested.  E.g., complex setups or asymmetric
configs on master and slave, etc. have NOT been tested.

This has also not been tested with redirected pushes.
2012-06-01 17:28:14 +05:30
Sitaram Chamarty 42e0bac48c 'perms' command learns to create repo if needed 2012-06-01 16:01:36 +05:30
Sitaram Chamarty d9df70a04f allow getting config settings for non-existant repos also
It's reasonable to want to see config items (for example, mirror
settings) that *would* apply if the repo existed.
2012-06-01 16:01:36 +05:30
Sitaram Chamarty 7170ad9124 allow pubkey filename as extra argument to command in authkeys 2012-05-30 15:02:35 +05:30
Sitaram Chamarty a64401bd9a (doc) document the INPUT trigger 2012-05-30 15:02:35 +05:30
Sitaram Chamarty 21dbe53d39 fix minor bug in handling 'desc = "some description"'
repo foo
        desc = "foo"
        RW  =   u1
        ...etc...

The order of the clauses was parsing this like the old 'reponame = "some
description"' and end up creating a repo called 'desc'!

It would also, as a side-effect, change the repo so what you thought
were access rules for 'foo' would become access rules for 'desc'.
2012-05-29 20:55:53 +05:30
Sitaram Chamarty 06d3398fb0 lock binary files... (manually tested)
Remember that true locking is not possible in a DVCS; see
doc/locking.mkd for details and limitations of what is offered here.
2012-05-27 19:06:46 +05:30
Sitaram Chamarty d623388c9f (BSD compat) apparently 'wc -l' on BSD adds spaces in front
Larry was right.  It's probably easier to port a shell than a shell
script!
2012-05-27 05:58:43 +05:30
Sitaram Chamarty 8595303c82 migrated symbolic-ref command 2012-05-27 05:58:43 +05:30
Sitaram Chamarty 6f740908bb (collected docfixes) 2012-05-27 05:58:43 +05:30
Sitaram Chamarty 84d123e124 add 2 new sections to "special features"...
- using pubkeys obtained from elsewhere
  - updating hooks via the admin repo
2012-05-25 15:53:30 +05:30
Sitaram Chamarty 17841e8208 gitolite setup learns --hooks-only option 2012-05-25 12:54:06 +05:30
Sitaram Chamarty e1d9aee98b delete the 'description' file for new repos 2012-05-25 12:54:06 +05:30
Sitaram Chamarty 37e97d29fe the 3 shipped post-create programs should exit when called on a normal repo creation 2012-05-25 12:47:12 +05:30
Sitaram Chamarty 0f3a09ce60 PRE_ and POST_CREATE should work for normal repos also 2012-05-25 12:47:12 +05:30
Sitaram Chamarty 04367af3e8 Revert "simulate POST_CREATE for newly created "normal" repos"
This reverts commit bc3eb34211.
2012-05-25 12:47:12 +05:30
Mike Kelly 62a66662be Properly migrate [gitosis] section 2012-05-23 05:08:22 +05:30
Sitaram Chamarty 75387fd6cb v3.03 2012-05-23 04:34:07 +05:30
Sitaram Chamarty 5298a79cb5 MAJOR BUGFIX: disallow "hooks" directory in admin repo
Although this is not a "hole" that allows a normal user to bypass
controls, I still consider this a hole in the sense that I want to
separate "admin push" rights from "shell access on server" rights.

(I realise that most people don't make this distinction, but I do, and
for me and most sites I consult for it is important).

Thanks to drue on #gitolite who pointed it out excitedly, and apologies
for killing what he thought of as a feature!
2012-05-23 04:33:54 +05:30
Sitaram Chamarty dd083085cf (fix bugly) info -ld should handle missing description files more gracefully
bugly = bug that makes the output ugly :)
2012-05-23 03:37:52 +05:30
Sitaram Chamarty 2907561529 (minor) solaris doesn't like "shift" when there are no arguments remaining 2012-05-22 13:17:10 +05:30
Sitaram Chamarty 55d64752ae fix repo alias to work when reponame has leading "/"
as in git@host:repo.git works but ssh://git@host/repo.git doesn't
2012-05-22 11:57:40 +05:30
Sitaram Chamarty b6ce11a19f (minor) permissions fixup -- sugar scripts do not need +x 2012-05-22 07:06:41 +05:30
Sitaram Chamarty b12a967272 update g2 compat and migr info
thanks to karihre on #gitolite for catching the first of the corrections
(GL_GET_MEMBERSHIPS_PGM) and so reminding me...
2012-05-22 07:05:33 +05:30
Sitaram Chamarty d04e79d291 (minor) single quotes around variables in error messages
(plus a couple of other minor fixups)
2012-05-21 17:44:30 +05:30
Sitaram Chamarty 20d2120ea5 move input command check so it works for non-ssh modes also 2012-05-21 15:24:40 +05:30
Sitaram Chamarty 3a59f5aff0 line up regexes for easier review 2012-05-21 15:24:40 +05:30
Sitaram Chamarty 8aba6ec2be v3.02 2012-05-20 16:42:43 +05:30
Sitaram Chamarty 72b6a54e0a test packaging instructions and make required changes 2012-05-20 14:21:23 +05:30
Sitaram Chamarty 27c0190b76 packaging instructions make analogy with git for better explanation 2012-05-20 06:38:18 +05:30
Sitaram Chamarty 8644690506 (ssh) make it easier to make give some users a full shell 2012-05-19 06:01:05 +05:30
Sitaram Chamarty 07169c37ec allow aliasing a repo to another name
all documentation is inside Alias.pm.
2012-05-19 06:01:05 +05:30
Sitaram Chamarty bc3eb34211 simulate POST_CREATE for newly created "normal" repos
See "background" in new program src/triggers/new-normal-repos
2012-05-16 18:55:06 +05:30
Sitaram Chamarty 17a680e0f6 (collected docfixes) 2012-05-16 18:54:52 +05:30
Sitaram Chamarty e511943a45 just for kicks, a VREF that allows voting on changes to a branch
(manually smoke tested; no test script)

I've been meaning to do this for a while, since someone told me that is
one of gerrit's features they like.

Of course, gitolite can't/won't do the whole "code review" thing (nor
the workflow enforcement that follows).

But voting is simple -- literally 2-3 lines of code in a VREF.  (The
rest is inline documentation).
2012-05-10 12:28:37 +05:30
Sitaram Chamarty fa2893be7c the dupkeys function was already in ssh-authkeys...
...so there's no need for the VREF.

Ironically, while I was arguing with Eli that I wouldn't do it and why,
the code was *already* there, and had been for over a month!  (It must
have been there for much longer for me to have forgotten!)

TODO: convert from using fingerprint compute to actual key strings when
the complaints about speed start appearing.

My own personal speed up loop [1] I guess :)

[1]: http://thedailywtf.com/Articles/Classic-WTF-The-Speedup-Loop.aspx
2012-05-07 15:08:46 +05:30
Sitaram Chamarty 699bafa096 (minor fixup to t/info.t) 2012-05-06 19:06:11 +05:30
Sitaram Chamarty e76be7ff11 move repo/user validity check deeper
(but change repo check to allow repoPATT instead of just repoNAME)

This is because there are/will be some situations where access() is
called without those two checks being done (i.e., it is not only from
src/commands/access that it is called).
2012-05-06 19:02:42 +05:30
Sitaram Chamarty 196706c145 bugfix: smart http mode wasn't running pre_ and post_ git triggers
(while we're about it, we also steal Michael Brown's idea (patch #2 in
[1] and get rid of GIT_HTTP_BACKEND).

[1]: http://groups.google.com/group/gitolite/msg/adfae758dd28f2a8
2012-05-05 18:07:15 +05:30
Sitaram Chamarty 6d057fb84c allow info to print description also 2012-05-04 18:13:42 +05:30
Sitaram Chamarty d8df4a9344 git-config bugfix + backward compat breakage in usage of 'config'
(1) the backward compat breakage: you can't create empty-valued config
    keys anymore.  That is, you can't do the eqvt of the following shell
    command using gitolite

        git config foo.bar ""

(2) fixed a bug where this:

        repo foo
            config foo.bar =

    when queried using

        gitolite git-config -r foo .

    would return even the empty valued ones, which -- remember! -- are
    not supposed to exist anymore.

    Fixing this bug allows situations like this to not show the admin
    repo in gitweb:

        repo [a-z].*
            config gitweb.owner = P-h B

        repo gitolite-admin
            config gitweb.owner =

----

background...

Somewhere in g3 (well actually in 057506b), we lost the ability to
distinguish
    config foo.bar  =   ""
from
    config foo.bar =

I decided that conflating them is more intuitive for most people,
because a survey [1] revealed that no one seemed to want the equivalent
of the following shell command:

----

[1] ...of a (small prime greater than 1) number of people on #git
2012-05-04 17:30:22 +05:30
Andreas Stenius 47a0c44540 migrated htpasswd command from g2.
(with some fixups by committer)
2012-05-03 11:04:22 +05:30
Sitaram Chamarty 49d132a969 minor fix to info command output under httpd...
when running under httpd, $ENV{USER} is not set, so we use a (hopefully
informative) default to print.

Thanks to Thomas Hager (duke at sigsegv dot at) for catching this.
2012-05-01 15:06:06 +05:30
Sitaram Chamarty c145528849 (minor typo fix) 2012-05-01 14:20:13 +05:30
Sitaram Chamarty 850882c1a6 allow VREF code to print to STDOUT...
Using a g2-style "chained update hook" as a VREF doesn't *quite* work:

  - all STDOUT from the hook is lost
  - worse, all lines get parsed as a ref followed by a message, and if
    the ref doesn't look like a ref it dies

So now we do all this only if the message starts with 'VREF/'.  Any
other output is just printed out as is.
2012-04-30 06:02:17 +05:30
Sitaram Chamarty 88c8d774d0 v3.01 2012-04-29 05:56:40 +05:30
Thomas Hager 88b4c86c38 Added instructions to make repositories available via http and ssh
This patch adds instructions for configuring Gitolite and Apache 2.x
to make repositories available to both ssh and http clients.

[minor fixups by committer]
2012-04-28 22:58:09 +05:30
Sitaram Chamarty 48ed4deb8f BSD compat changes
thanks to milki for all the efforts!

Details:

  - partial-copy fell afoul of BSD not having $RANDOM
  - test suite: fix bad GNU sort with good perl sort
  - test suite: fix md5sum dependency (which BSD doesn't have or can't
    easily have or requires extra options or whatever...), by doing it
    in perl.  (Requires Digest::MD5, which is probably available
    anyway, but since this is only for the test suite, meh!)
2012-04-28 22:58:09 +05:30
Sitaram Chamarty e919a0b7ca solaris doesn't like 'hostname -s'...
(luckily, unlike linux, it doesn't spew a usage message to STDOUT!)
2012-04-28 12:04:51 +05:30
Sitaram Chamarty 7d6b04605d fix test suite's dependency on time zone
I had not remembered that the 'tc' subcommand in tsh adds *text* that
contains the current time, so commit SHAs were changing.

Thanks to milki for catching this, and in fact being the only person who
ever appears to have attempted to run the test suite at all!
2012-04-28 04:55:49 +05:30
Sitaram Chamarty eabcf83dee (minor) add titles to rendered HTML docs 2012-04-26 16:37:48 +05:30
Sitaram Chamarty a952f2d627 add COPYING file
(from http://www.gnu.org/licenses/gpl-2.0.txt)

thanks to Jon Ciesla for catching this omission...
2012-04-26 16:06:03 +05:30
Sitaram Chamarty e0ed14172b add migration example, plus some other little mig doc fixes 2012-04-24 15:07:08 +05:30
Sitaram Chamarty 9e1cb5936c (some docfixes) 2012-04-24 14:10:40 +05:30
Sitaram Chamarty 5d366b5c0e new VREF: MAX_NEWBIN_SIZE (manual spot testing only) 2012-04-23 17:27:00 +05:30
Sitaram Chamarty d74f596e23 make can_write() in Easy.pm more flexible 2012-04-23 17:27:00 +05:30
Sitaram Chamarty 198dcfd4c8 POST_CREATE efficiency... (please read below if you care)
The POST_CREATE trigger is called when

  * a user creates a new "wild" repo,
  * a user uses the "perms" command, and
  * a user uses the "fork" command.

The trigger calls 3 programs (see rc file):

    post-compile/update-git-configs
    post-compile/update-gitweb-access-list
    post-compile/update-git-daemon-access-list

(They are also called by the POST_COMPILE trigger, by the way.)

However, the 3 programs shown are a bit wasteful -- they run through
*all* the repos when really only *one* repo has been affected.

This patch

  * passes the repo name to the 3 programs (duh!)

  * adds the optimisation to the first of the 3 programs listed above
    (the one dealing with 'git config').

For the other two programs (gitweb and git-daemon), you have 3 choices:

  * if you don't have too many repos, ignore the problem.

  * take out the 2nd and 3rd lines from the POST_CREATE list in the rc
    file, so they don't run.

    Then run 'gitolite trigger POST_COMPILE' from cron at regular
    intervals.  (Note that is POST_COMPILE not POST_CREATE!)  However,
    this means that gitweb and daemon permissions won't be current
    immediately after someone adds a new repo or sets perms etc.; they
    get updated only on the next cron run.

  * patch the programs to add this optimisation (and send me the
    patches).  The optimisation would check if arg-1 ($1 in shell,
    $ARGV[0] in perl) is 'POST_CREATE', and if it is, take the *next*
    argument as a repo name that may have changed.
2012-04-22 22:44:39 +05:30
Sitaram Chamarty 895b3614ed (minor) add a bit more detail on usage text for 'info' 2012-04-22 22:43:45 +05:30
Sitaram Chamarty 6b65e7853f (minor) add quotes to make repo name stand out in error message 2012-04-22 22:43:45 +05:30
Sitaram Chamarty 1ad0a761f7 install should fail more gracefully if the '-ln' directory does not exist
thanks to EugeneKay for catching this
2012-04-22 22:43:27 +05:30
Sitaram Chamarty c3ec518cef fork command, and some core changes to make it work...
- access command allows checking ^C
  - ^C check will fail when the repo exists
2012-04-22 16:56:21 +05:30
Sitaram Chamarty cf3dd885fc (some docfixes) 2012-04-21 22:29:56 +05:30
Thomas Hager 3a7b547759 replaced /bin/echo with printf, Solaris echo doesn't recognize -n
gitolite setup fails to check admin pubkey, because $text always
contains 2 or more lines after tsh_try() (the key and -n).

[committer adds:
    I wasn't sure if 'printf' would work on cygwin, so I chose what
    looked like a safer option, but apparently it wasn't safe enough and
    fell afoul of Solaris.

    Anyway I managed to check (using a small test program) with someone
    who runs gitolite on cygwin, and it works.

    If you're wondering why I didn't just use echo followed by chomp(),
    that would of course have been the easy way out but I wanted to see
    how you'd do it without a post-processing option.  It became a
    frustrating challenge of sorts because it seems such a trivial thing!
]
2012-04-18 14:19:51 +05:30
Thomas Hager aaccb367ec changes to support Solaris default shell
Solaris default bourne shell does not recognize $(), and does not allow
exporting a variable and assigning a value to it in one step.
2012-04-18 13:42:50 +05:30
Sitaram Chamarty 51833fccfb added new changelog 2012-04-18 06:53:50 +05:30
Sitaram Chamarty 2c8e0dfd2f (doc) general cleanup of docs 2012-04-18 06:39:17 +05:30
Sitaram Chamarty 95e6c2ae8b (doc) switch from mindmap to mkd...
we don't need the flexibility any more
2012-04-18 06:38:33 +05:30
Sitaram Chamarty 9006b07d2e (doc) switch g2 <-> g3 2012-04-18 06:38:33 +05:30
Sitaram Chamarty 1dc68b540d (access.t) added a specific rule accum test 2012-04-18 06:26:53 +05:30
Sitaram Chamarty 1c15b4cc2d (perltidy) 2012-04-18 06:26:53 +05:30
Sitaram Chamarty 2cb7d8313e (minor) make trigger function name consistent with other similar uses
writable() in Writable.pm renamed to "access_1" to be consistent; i.e.,
reflect the trigger name
2012-04-18 06:23:21 +05:30
Sitaram Chamarty 2629d7f00a (minor) add remote tests for the 'writable' command 2012-04-18 06:23:21 +05:30
Sitaram Chamarty 581e79d745 logging die and warn messages
- change a few important die()s to _die()s
  - setup SIGs for both die and warn so any others will get caught
2012-04-18 06:23:21 +05:30
Sitaram Chamarty 67327ebfb4 (minor) add svnserve command 2012-04-18 06:23:21 +05:30
Sitaram Chamarty 273e6fd627 "fake Unix" strikes again...
The fix is easy enough, but I hate having to code work-arounds for
proprietary OSs when the same code works fine on Linux and BSD.

/me wisely avoids words like posix in his rant ;-)

Thanks to Franck Zoccolo for help in finding what the problem was and
when and why it occurred.

----

Someday there will be some issue that requires a fix with significant
code change (or worse, a change that is incompatible with Linux), and I
will probably refuse.  Of course, I will be properly regretful about my
inability to fix it.[1]
2012-04-17 14:14:28 +05:30
Sitaram Chamarty b5024027ca yaaay! http is finally done! 2012-04-15 21:14:56 +05:30
Sitaram Chamarty b60dd9c349 (minor) packaging notes updated 2012-04-14 13:03:54 +05:30
Sitaram Chamarty 04a6f75e5c (doc updates, mostly migration)
- migration and non-core reachable from master-toc now
  - migration flow changed.  install.mkd, migration section, is [migr]
    now, drives the whole thing now, links to g2migr
  - more details on how to wipe out old gitolite

plus some minor fixes
2012-04-13 15:35:13 +05:30
Sitaram Chamarty 720729e4b4 (minor) do not run `gitolite query-rc` from *perl* programs!
I must have blindly converted from some shell-thinking/shell-code for
these to have slipped through!

(found when doing an audit of all system, exec, ``, qx, and tsh_)
2012-04-13 15:35:13 +05:30
Sitaram Chamarty afc2c14a65 (minor) t/reset and test scripts were not getting a VERSION file 2012-04-11 19:06:13 +05:30
Sitaram Chamarty 4c5bb27739 pre-existing repo instructions were WRONG...
- fix them
  - but (at the cost of some efficiency) try to compensate if the admin
    did not follow those instructions, by running hook_1 anyway
2012-04-11 19:03:17 +05:30
Sitaram Chamarty 8c28fd2241 D...
(manually tested, no test script)

the whimsically named "D" command deletes repos, and is the opposite of
the "C" permission that enables the user to create one in the first
place.  See the usage message for user info, and look in the comments of
the code itself for admin info.
2012-04-11 07:06:45 +05:30
Sitaram Chamarty bbaacfaee7 (mostly) doc changes
- minor typo fixes, clarifications, etc.

  - keep sts.html url consistent, because many people link to
    http://sitaramc.github.com/gitolite/sts.html

  - create a common migration doc, so the old 'migr.html' does not 404
    when g3 docs become "main"

  - progit doc done

  - add gitosis convert script (FWIW)

  - a minor comment fix to Sugar.pm
2012-04-10 15:41:32 +05:30
Sitaram Chamarty c5e0e929a7 sskm: minimum changes, minimally smoke tested!
(thanks to fabian@hirschm.net for testing more thoroughly as well)
2012-04-10 04:23:57 +05:30
Sitaram Chamarty 48f1e7c781 'gitolite git-config' should print only value when regex not used...
not repo<tab>key<tab>value.  Also, honor '-n' (no newline)
2012-04-08 11:31:04 +05:30
Sitaram Chamarty 39fc5b32bb document overhaul 2 - mostly migration stuff 2012-04-08 08:13:31 +05:30
Sitaram Chamarty 7858beb541 (mkdoc) allow internal [[TOC]] again 2012-04-08 07:32:46 +05:30
Sitaram Chamarty 55e9b47bd1 CpuTime module learns to compute elapsed time 2012-04-08 06:07:14 +05:30
Sitaram Chamarty d3610191d3 supporting DOS and fake Unix...
I was very, very, tempted to say "sorry; not supported".  Sadly,
prudence won over juvenile glee...

PS: DOS == dominant operating system
2012-04-06 21:13:56 +05:30
Sitaram Chamarty 6e5c9954fd upgrade instructions forgot about the VERSION file update! 2012-04-06 17:26:27 +05:30
Sitaram Chamarty 057506b73f remove quotes around option values
for example, this now works (it used to save the quotes also)

    option mirror.master = "ilh-95"
2012-04-06 17:26:27 +05:30
Sitaram Chamarty e1c7e546aa cpu-time command -> CpuTime trigger module...
...now that triggers are not restricted to external programs and can be
perl code called by gitolite-shell (thus in the same PID), there's no
need to compute and pass along the times() array.

This also changes the arguments to POST_GIT; they're now the same as
PRE_GIT's.
2012-04-06 17:26:27 +05:30
Sitaram Chamarty de40461d9a document overhaul
- explicit 'list' gives way to mindmap, ...
  - 'fm2mt.pl' to produce master-toc.mkd from the mindmap
  - mkdoc no longer ignores master-toc.mkd, calls fm2mt.pl itself

and LOTS of changes to the actual docs
2012-04-05 21:42:22 +05:30
Sitaram Chamarty 7c6728af89 (some minor changes)
- whitespace change to t/reset
  - remove dbg line accidentally left in in Load.pm
  - add a bit more explanation to 'writable'
2012-04-05 21:31:59 +05:30
Sitaram Chamarty 9bbc5703e3 CSS text-color specified...
...so it is readable for people who use black bg browsers
2012-04-05 19:19:36 +05:30
Sitaram Chamarty ecb172b785 new README, in preparation for rolling the new release into "master" 2012-04-05 19:19:36 +05:30
Sitaram Chamarty 8df28a02db (minor) comments and clarifications in default rc 2012-04-04 05:01:10 +05:30
Sitaram Chamarty 495390926d added sshkeys-lint as a command 2012-04-03 17:15:13 +05:30
Sitaram Chamarty 35953a5bd3 added 'gitolite push' to make server side pushes easier...
also force the update hook to log SHAs of bypassed pushes
2012-04-03 16:40:06 +05:30
Sitaram Chamarty ad77cef7de (mainly for fedora) '-s' gets a shell. Manual spot-testing only
also includes "use $USER if username not passed"
2012-04-03 15:06:08 +05:30
Sitaram Chamarty 81b503d2bd GROUPLIST_PGM; manually spot-tested, no test script. PW. 2012-04-03 09:47:31 +05:30
Sitaram Chamarty 9b3efb9084 mirroring: docs and check-g2-compat 2012-04-02 13:18:30 +05:30
gitolite tester 8e8ccb50f4 additions to default rc (commented out) for mirroring 2012-04-02 13:18:30 +05:30
gitolite tester 25bb1c00db mirroring without sausages
(or at least without showing the making of said sausages)
2012-04-02 13:18:30 +05:30
Sitaram Chamarty b78466b164 put the VERSION file in the right place
I don't know why I had put VERSION in GL_ADMIN_BASE, which is pretty
stupid.  It should be in GL_BINDIR.

It also has nothing to do with setup -- the file needs to be generated
at 'install' time.
2012-04-02 13:18:30 +05:30
159 changed files with 5340 additions and 4847 deletions

86
CHANGELOG Normal file
View 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.

278
COPYING Normal file
View File

@ -0,0 +1,278 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.

17
INSTALL
View File

@ -1,17 +0,0 @@
1. Clone the repo and copy src somewhere (or leave it where it is, if you're
sure no one will 'git pull' on a running system!)
cp -a src /some/full/path
2. (Optional) Make a symlink for the single executable 'gitolite' to
somewhere in `$PATH`
ln -sf /full/path/to/some/damn/place/gitolite $HOME/bin
3. Run setup. That is, either run:
gitolite setup -a YourName -pk /tmp/YourName.pub
or, if you did not do step 2, run:
/some/full/path/src/gitolite -a YourName -pk /tmp/YourName.pub

359
README.md Normal file
View 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/

View File

@ -38,8 +38,9 @@ sub intro {
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 doc/g2migr.mkd");
msg( '', => "(online at http://sitaramc.github.com/gitolite/g3/g2migr.html)" );
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( '', '' );
}
@ -52,16 +53,15 @@ sub rc_basic {
}
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_GET_MEMBERSHIPS_PGM not yet implemented" ) if $GL_GET_MEMBERSHIPS_PGM;
msg( SEVERE => "GL_NO_CREATE_REPOS not yet implemented" ) if $GL_NO_CREATE_REPOS;
msg( SEVERE => 'htpasswd, rsync, and svnserve not yet implemented' ) if $HTPASSWD_FILE or $RSYNC_BASE or $SVNSERVE;
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; many ADCs not yet implemented") if $GL_ADC_PATH;
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';
}
@ -70,8 +70,9 @@ sub conf {
chdir($GL_ADMINDIR);
my $conf = `find . -name "*.conf" | xargs cat`;
msg( "SEVERE", "fallthru in NAME rules; see docs" ) if $conf =~ m(NAME/);
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 {

127
convert-gitosis-conf Executable file
View 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";
#}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,34 +0,0 @@
# adding users and repos
Do NOT add repos directly on the server. Clone the 'gitolite-admin' repo to
your workstation, make changes to it, then add, commit, and push. When the
push hits the server, the server "acts" upon your changes.
Full documentation on the conf file is [here][conf].
Here's a sample sequence, on your workstation, after your install is done
git clone git@host:gitolite-admin
cd gitolite-admin
vi conf/gitolite.conf
# now add lines like these:
repo foo
RW+ = me
RW = alice
R = wally
# now save the file and add it
git add conf
# add a couple of users; get their pubkeys by email or something, then:
cp /some/where/alice.pub keydir
cp /else/where/wally.pub keydir
git add keydir
# now commit and push
git commit -m 'added repo foo'
git push
# at this point gitolite will create the new repo 'foo' (if it did not
# already exist) then update the authorized keys file to include alice and
# wally's pubkeys

View File

@ -1,31 +0,0 @@
# the gitolite.conf file
This file is the crux of all of gitolite's access control. The basic syntax
is very simple.
Note: `<user>+` means one or more user or user group names, `<repo>+` means
one or more repo or repo group names, and `<refex>*` means zero or more
refexes.
* [group][group] definitions (optional, for convenience)
@<group> = <user>+
@<group> = <repo>+
* [repo][repo] definitions and access [rules][]
repo <repo>+
<perm> <refex>* = <user>+
# one or more such lines
* [gitolite options][options] that apply to the repo(s) in the last
"repo ..." line, for example:
option deny-rules = 1
* [git config][git-config] keys and values that also apply to the last named
repo(s), for example:
config hooks.emailprefix = '[%GL_REPO] '
In addition, you can also have [include][] statements.

View File

@ -1,74 +0,0 @@
# customising gitolite
Much of gitolite (g3)'s functionality comes from programs and scripts that are
not considered "core". This keeps the core simpler, and allows you to enhance
gitolite for your own purposes without too much fuss.
## types of non-core programs
There are 5 basic types of non-core programs.
* *commands* can be run from the shell command line. Those listed in the
COMMANDS hash of the rc file can also be run remotely.
* *hooks* are standard git hooks; see below.
* *sugar scripts* change the conf language for your convenience. The word
sugar comes from "syntactics sugar".
* *triggers* are to gitolite what hooks are to git. I just chose a
different name to avoid confusion and constant disambiguation in the docs.
* **VREFs** are extensions to the access control check part of gitolite.
[Here][non-core] is a list of non-core programs shipped with gitolite, with
some description of each.
## #commands gitolite "commands"
Gitolite comes with several commands that users can run. Remote user run the
commands by saying:
ssh git@host command-name [args...]
while on the server you can run
gitolite command [args...]
Very few commands are designed to be run both ways, but it can be done, by
checking for the presence of env var `GL_USER`.
You can get a **list of available commands** by using the `help` command.
Naturally, a remote user will see a much smaller list than the server user.
You add commands to the "allowed from remote" list by adding its name (or
uncommenting it if it's already added but commented out) to the COMMANDS hash
in the [rc][] file.
If you write your own commands, put them in src/commands.
## #hooks hooks and gitolite
Gitolite uses the `update` hook for all repos. In addition, it uses the
`post-update` hook for the gitolite-admin repo.
If you want to add your own hook, it's easy as long as it's not the 'update'
hook. Just add it to `$HOME/.gitolite/hooks/common` and run `gitolite setup`.
The rest is between you and 'man githooks' :-)
## #sugar syntactic sugar
Sugar scripts help you change the perceived syntax of the conf language. The
base syntax of the language is as described [here][conf], so sugar scripts
take something *else* and convert it into that.
That way, the admin sees additional features (like allowing continuation
lines), while the parser in the core gitolite engine does not change.
If you want to write your own sugar scripts, please read the "your own sugar"
section in [dev-notes][] first then email me.
## triggers
Triggers have their own [document][triggers].
## VREFs
VREFs also have their own [document][vref].

View File

@ -1,118 +0,0 @@
# notes for developers
Gitolite has a huge bunch of existing features that gradually need to moved
over. Plus you may want to write your own programs to interact with it.
Hints for developers wishing to help migrate features over from g2 are
[here][dev-hints].
Here are some random notes on developing hooks, commands, triggers, and sugar
scripts.
## environment variables and other inputs
In general, the following environment variables should always be available:
GL_BINDIR
GL_REPO_BASE
GL_ADMIN_BASE
Commands invoked by a remote client will also have `GL_USER` set. Hooks will
have `GL_REPO` also set.
Finally, note that triggers get a lot of relevant information as arguments;
see [here][triggers] for details.
## APIs
### the shell API
The following commands exist to help you write shell scripts that interact
easily with gitolite. Each of them responds to `h` so please run that for
more info.
* `gitolite access` to check access rights given repo, user, type of access
(R, W, ...) and refname (optional). Example use: src/commands/desc
* `gitolite creator` to get/check the creator of a repo. Example use:
src/commands/desc
* `gitolite git-config` to check gitolite options or git config variables
directly from gitolite's "compiled output, (i.e., without looking at the
actual `repo.git/config` file or using the `git config` command). Example
use: none yet
* `gitolite query-rc` to check the value of an RC variable. Example use:
src/commands/desc.
In addition, you can also look at the comments in src/Gitolite/Easy.pm (the
perl API module) for ideas.
### the perl API
...is implemented by Gitolite::Easy; the comments in src/Gitolite/Easy.pm
serve as documentation.
## your own hooks
### anything but the update hook
If you want to add your own hook, it's easy as long as it's not the 'update'
hook. Just add it to `$HOME/.gitolite/hooks/common` and run `gitolite setup`.
The rest is between you and 'man githooks' :-)
### update hook
If you want to add additional `update` hook functionality, do this:
* write and test your update hook separately from gitolite
* now add the code to src/VREF. Let's say it is called "foo".
* to call your new update hook to all accesses for all repos, add this to
the end of your conf file:
repo @all
- VREF/foo = @all
As you probably guessed, you can now make your additional update hooks more
selective, applying them only to some repos / users / combinations.
Note: a normal update hook expects 3 arguments (ref, old SHA, new SHA). A
VREF will get those three, followed by at least 4 more. Your VREF should just
ignore the extra args.
## your own commands
You can add your own commands. You can run them on the server (example,
`gitolite access`). Then you can enable certain commands to be allowed to run
by a remote user by adding them to the "COMMANDS" hash of the [rc][] file.
Commands are standalone programs, in any language you like. They simply
receive the arguments you append. In addition, the env var `GL_USER` is
available if it is being run remotely. src/commands/desc is the best example
at present.
## your own trigger programs
Trigger programs are just commands whose names have been added to the
appropriate list in the [rc][] file. Triggers get specific arguments
depending on when they are called; see [here][triggers] for details.
You can write programs that are both manually runnable as well as callable by
trigger events, especially if they don't *need* any arguments.
Look in the distributed [rc][] file for example programs; at this point there
aren't many.
## your own "sugar"
Syntactic sugar helpers are NOT complete, standalone, programs. They must
include a perl sub called `sugar_script` that takes in a listref, and returns
a listref. The listrefs point to a list that contains the entire conf file
(with all [include][] processing already done). You create a new list with
contents modified as you like and return a ref to it.
There are a couple of examples in src/syntactic-sugar.

View File

@ -1,35 +0,0 @@
## #dev-status g3 development status
Not yet done (will be tackled in this order unless someone asks):
* detailed documentation for new features
* querying the outside world for group info (see gl-get-membership-program
in g2)
* mirroring
* pulling in documentation for things that are the same in g2
* "unrestricted arguments" for some ADCs (like git-annexe)
* smart http
* special features (no-create-repos, shell-access, gl-all-read-all, etc)
Help needed:
* I'd like distro packagers to play with it and help with migration advice
for distro-upgrades
* [rsync][pw2], htpasswd
* git-annexe support (but this has a pre-requisite in the previous list)
Won't be done unless someone asks (saw no evidence that anyone used them in g2
anyway!):
* mob branches
* password access
* specific ADCs -- there are too many for me to bother without applying
Pareto somewhere, so I choose to not do any and wait for people to ask :-)
Done:
* core code
* test suite
* some documentation
* distro packaging instructions
* migration advice for common cases

View File

@ -1,89 +0,0 @@
# authentication versus authorisation
This document will explain why an "ssh issue" is almost never a "gitolite
issue", and, indirectly, why I dont get too excited about the former.
Note: for actual ssh troubleshooting see [this][ssh-troubleshooting].
Here is a fundamental point: <font color="red">**Gitolite does not do
authentication. It only does authorisation**.</font>
So first, let's loosely define these words:
> **Authentication** is the process of verifying that you are who you claim
> to be. An authentication system will establish that I am the user
> "sitaram" on my work system. The one behind gmail will similarly
> establish that I am "sitaramc". And so on...
> **Authorisation** is the process of asking what you want to do and
> deciding if you're allowed to do it or not.
Now, if you managed to read about [gitolite and ssh][gitolite-and-ssh], you
know that gitolite is meant to be invoked as:
/full/path/to/gl-auth-command some-authenticated-gitolite-username
(where the "gitolite username" is a "virtual" username; it does not have to
be, and usually *isn't*, an actual *unix* username).
As you can see, authentication happens before gitolite is called.
## but... but... you have all that ssh stuff in there!
The default mode of using gitolite does use ssh keys, but all it's doing is
helping you **setup** ssh-based authentication **as a convenience to you**.
You don't have to use it, though. And many people don't. The examples I know
are [smart http][http], and ldap-backed sshd. In both cases, gitolite has no
role to play in creating users, setting up their passwords/keys, etc. There's
even a `GL_NO_SETUP_AUTHKEYS` option to make sure gitolite doesn't meddle with
the authkeys file in such installations.
## so you're basically saying you won't support "X"
(where "X" is some ssh related behaviour change or feature)
Well, if it's not a security issue I *probably* won't. I'm willing to change
my mind if enough people convince me they need it. (There's a mailing list if
you want to find others who also need the same thing.)
While we're on the subject, locking someone out is *not* a security issue.
Even if you locked yourself (the admin) out, the docs tell you how to recover
from such errors. You do need some password based method to get a shell
command line on the server, of course.
## appendix: how to use other authentication systems with gitolite
The bottom line in terms of how to invoke gitolite has been described above,
and as long as you manage to do that gitolite won't even know how the
authentication was done. Which in turn means you can use whatever
authentication scheme you want.
It also expects the `SSH_ORIGINAL_COMMAND` environment variable to contain the
full command (typically starting with git-receive-pack or git-upload-pack)
that the client sent. Also, when using [smart http][http], things are somewhat
different: gitolite uses certain environment variables that it expects httpd
to have set up. Even the user name comes from the `REMOTE_USER` environment
variable instead of as a command line argument in this case.
However, it has to be an authentication system that is compatible with sshd or
httpd in some form. Why? Because the git *client* accessing the server only
knows those 2 protocols to "speak git". (Well, the `git://` protocol is
unauthenticated, and `file://` doesn't really apply to this discussion, so
we're ignoring those).
For example, let's say you have an LDAP-based authentication system somewhere.
It is possible to make apache use that to authenticate users, so when a user
accesses a git url using `http://sitaram:password@git.example.com/repo`, it is
LDAP that does the actual authentication. [I wouldn't know how to do it but I
know it is possible. Patches to this doc explaining how are welcome!]
There are also ssh daemons that use LDAP to store the authorised keys (instead
of putting them all in `~/.ssh/authorized_keys`). The clients will still need
to generate keypairs and send them to the admin, but they can be more
centrally stored and perhaps used by other programs or tools simultaneously,
which can be useful.
Finally, gitolite allows you to store *group* information externally too. See
[here][ldap] for more on this.

View File

@ -1,145 +0,0 @@
## #glssh how gitolite uses ssh
Although other forms of authentications exist (see the document on
[authentication versus authorisation][auth]), ssh is the one that most git
users use.
***Therefore, gitolite is (usually) heavily dependent on ssh***.
Most people didn't realise this, and even if they did they don't know ssh
well enough to help themselves. If you don't understand how ssh public key
authentication works, or how the `~/.ssh/authorized_keys` file can be used to
restrict users, etc., you will have endless amounts of trouble getting
gitolite to work, because you'll be attacking the wrong problem.
So please please please understand this before tearing your hair out and
blaming ***git/gitolite*** for whatever is going wrong with your setup :-)
### ssh basics
Let's start with some basics, focusing *only* on the pieces relevant to
`gitolite`. If this is not detailed enough, please use google and learn more
from somewhere, or maybe buy the OReilly ssh book.
* You can login to an ssh server by typing a password, but ssh can also use
***public-private keys*** (also called "key pairs") for authentication.
`gitolite` *requires* you to use this mechanism for your users -- they
cannot log in using passwords. Hopefully by the time you finish reading
this document you will understand why :-)
The way you set this up is you generate a key pair on your workstation,
and give the server the public key. (I need not add that the "private"
key must be, well, kept *private*!)
* **generating a key pair on your workstation** is done by running the
command `ssh-keygen -t rsa`. This produces two files in `~/.ssh`. One is
`id_rsa`; this is the **private** key -- ***never*** let it out of your
machine. The other is `id_rsa.pub`, which is the corresponding public
key. This public key is usually just one long line of text.
* on Windows machines with msysgit installed, you should do this from
within a "git bash" window. The command will report the full path where
the files have been written; make a note of this, and use those files in
any of the description that follows
* **adding your public key to the server**'s `~/.ssh/authorized_keys`
file is how ssh uses pubkeys to authenticate users. Let's say
sita@work.station is trying to log in as git@serv.er. What you have to do
is take the `~/.ssh/id_rsa.pub` file for user sita on work.station and
append its contents (remember it's only one line) to
`~/.ssh/authorized_keys` for user git on serv.er.
The `authorized_keys` file can have multiple public keys (from many
different people) added to it so any of them can log in to git@serv.er.
In the normal case (not gitolite, but your normal everyday shell access),
there's a command that does this, `ssh-copy-id`, which also fixes up
permissions etc., as needed, since sshd is a little picky about allowing
pubkey access if permissions on the server are loose. Or you can do it
manually, as long as you know what you're doing and you're careful not to
erase or overwrite the existing contents of `~/.ssh/authorized_keys` on
the server!
But in the gitolite case, it's different; we'll get to that in a minute.
* **troubleshooting pubkey authentication failures**: if you are unable to
get ssh access to the server after doing all this, you'll have to look
in `/var/log/secure` or `/var/log/auth.log` or some such file on the
server to see what specific error `sshd` is complaining about.
* **restricting users to specific commands** is very important for gitolite.
If you read `man sshd` and look for `authorized_keys file format`, you'll
see a lot of options you can add to the public key line, which restrict
the incoming user in various ways. In particular, note the `command=`
option, which means "regardless of what the incoming user is asking to do,
forcibly run this command instead".
Also note that when there are many public keys (i.e., lines) in the
`authorized_keys` file, each line can have a *different* set of options
and `command=` values.
Without this `command=` option, the ssh daemon will simply give you a
shell, which is not what we want for our gitolite keys (although we may
well have other keys which we use to get a shell).
**This is the backbone of what makes gitolite work; please make sure you
understand this**.
### how does gitolite use all this ssh magic?
These are two different questions you ought to be having by now:
* how does it distinguish between me and someone else, since we're all
logging in as the same remote user "git"
* how does it restrict what I can do within a repository
#### restricting shell access/distinguishing one user from another
The answer to the first question is the `command=` we talked about before. If
you look in the `authorized_keys` file, you'll see entries like this (I chopped
off the ends of course; they're pretty long lines):
command="[path]/gl-auth-command sitaram",[more options] ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA18S2t...
command="[path]/gl-auth-command usertwo",[more options] ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEArXtCT...
First, it finds out which of the public keys in this file match the incoming
login. That's crypto stuff, and I won't go into it. Once the match has been
found, it will run the command given on that line; e.g., if I logged in, it
would run `[path]/gl-auth-command sitaram`. So the first thing to note is
that such users do not get "shell access", which is good!
Before running the command, however, sshd sets up an environment variable
called `SSH_ORIGINAL_COMMAND` which contains the actual git command that your
workstation sent out. This is the command that *would have run* if you did
not have the `command=` part in the authorised keys file.
When `gl-auth-command` gets control, it looks at the first argument
("sitaram", "usertwo", etc) to determine who you are. It then looks at the
`SSH_ORIGINAL_COMMAND` variable to find out which repository you want to
access, and whether you're reading or writing.
Now that it has a user, repository, and access requested (read/write), gitolite looks
at its config file, and either allows or rejects the request.
But this cannot differentiate between different branches within a repo; that
has to be done separately.
#### restricting branch level actions
[If you look inside the git source tree, there's a file among the "howto"s in
there called `update-hook-example.txt`, which was the inspiration for this
part of gitolite.]
Git allows you to specify many "hooks", which get control as various events
happen -- see `git help hooks` for details. One of those hooks is the
`update` hook, which, if it is present, is invoked just before a branch or a
tag is about to be updated. The hook is passed the name of the branch or tag,
the old SHA1 value, and the new SHA1 value, as arguments. Hooks that are
called *before* an action happens are allowed to prevent that action from
happening by returning an error code.
When gitolite is told to create a new repository (by the admin), it installs
a special update hook. This hook takes all the information presented, looks
at the config file, and decides to allow or reject the update.
And that's basically it.

View File

@ -1,64 +0,0 @@
# not a gitolite problem
These are issues I do not want to be emailed about. That does not mean you
cannot get help -- in all cases, you're welcome to ask on [irc or the mailing
list][contact]. Irc especially has people with much more patience than I
have, God bless them...
## specific clients, or specific server OSs
These are things I can not support. That does not mean they will not work
with gitolite -- on the contrary, lots of people are using them.
But I personally don't use them, and I won't use them, and in my admittedly
limited experience they have given me good reason to stay well away.
Please ask for help on the [mailing list or IRC][contact]. Please do not
email me directly.
* putty/plink
* jgit/Eclipse
* Mac OS client or server
* windows as a server
* ...probably some more I forgot; will update this list as I remember...
## ssh
The *superstar* of the "not a gitolite problem" category is actually ssh.
Surprised? It is so common that it has [its own document][auth] to tell
you why it is *not* a gitolite problem, while [another one][ssh] tries to
help you anyway!
Everything I know is in that latter link. Please email me about ssh ONLY if
you find something wrong or missing in those documents.
## git
Example 1: when a first `git push` to a new repo fails, it is not because of
gitolite, it is because you need to say `git push origin master` or something.
This is a git issue.
There are several such examples. Gitolite is designed to look like just
another bare repo server to a client (except requiring public keys -- no
passwords allowed). It is *completely transparent* when there is no
authorisation failure (i.e., when the access is allowed, the remote client has
no way of knowing gitolite was even installed!)
Even "on disk", apart from reserving the `update` hook for itself, gitolite
does nothing to your bare repos unless you tell it to (for example, adding
'gitweb.owner' and such to the config file).
BEFORE you think gitolite is a problem, try the same thing with a normal bare
repo. In most cases you can play with it just by doing something like this:
mkdir /tmp/throwaway
cd /tmp/throwaway
git clone --mirror <some repo you have a URL for> bare.git
git clone bare.git worktree
cd worktree
<...try stuff>
----
In addition, the original nagp has more funny stuff...

View File

@ -1,34 +0,0 @@
# extremely brief regex overview
Regexes are powerful. Gitolite uses that power as much as it can. If you
can't handle that power, hire someone who can and become a manager.
That said, here's a very quick overview of the highlights.
`^` and `$` are called "anchors". They anchor the match to the beginning and
end of the string respectively.
^foo matches any string starting with 'foo'
foo$ matches any string ending with 'foo'
^foo$ matches exact string 'foo'.
To be precise, the last one is "any string starting and ending with *the same*
'foo'". "foofoo" does not match.
`[0-9]` is an example of a character class; it matches any single digit.
`[a-z]` matches any lower case alpha, and `[0-9a-f]` is the range of hex
characters. You should now guess what `[a-zA-Z0-9_]` does.
`.` (the period) is special -- it matches any character. If you want to match
an actual period, you need to say `\.`.
`*`, `?`, and `+` are quantifiers. They apply to the previous token. `a*`
means "zero or more 'a' characters". Similarly `a+` means "one or more", and
`a?` means "zero or one".
As a result, `.*` means "any number (including zero) of any character".
The previous token need not be a single character; you can use parens to make
it longer. `(foo)+` matches one or more "foo", (like "foo", "foofoo",
"foofoofoo", etc.)

View File

@ -1,427 +0,0 @@
## #sts ssh troubleshooting
**This document must be read in full the first time. If you start from some
nice looking section in the middle it may not help you unless you're already
an expert at ssh**.
This document should help you troubleshoot ssh-related problems in installing
and accessing gitolite.
### IMPORTANT -- READ THIS FIRST
#### caveats
* Before reading this document, it is **mandatory** to read and **completely
understand** [this][ssh], which is a very detailed look at how gitolite
uses ssh's features on the server side. Don't assume you know all that;
if you knew it, you wouldn't be needing *this* document either!
* This document, and others linked from this, together comprise all the help
I can give you in terms of the ssh aspect of using gitolite. If you're
installing gitolite, you're a "system admin", like it or not. Ssh is
therefore a necessary skill. Please take the time to learn at least
enough to get passwordless access working.
* Please note that authentication is not really gitolite's job at all. I'd
rather spend time on actual gitolite features, code, and documentation
than authentication (i.e., ssh, in the common case).
Surprised? [This][auth] might help explain better.
#### naming conventions used
* Your workstation is the **client**. Your userid on the client does not
matter, and it has no relation to your gitolite username.
* the server is called **server** and the "hosting user" is **git**. If
this is an RPM/DEB install, the hosting user is probably called
"gitolite", however we will use "git" in this document.
#### taking stock -- relevant files and directories
* the client has a `~/.ssh` containing a few keypairs. It may also have a
`config` file.
* the client also has a clone of the "gitolite-admin" repo, which contains a
bunch of `*.pub` files in `keydir`. We assume this clone is in `$HOME`;
if it is not, adjust instructions accordingly when needed.
* The git user on the server has a `~/.ssh/authorized_keys` file that the
ssh daemon uses to authenticate incoming users. We often call this file
**authkeys** to save typing, and it always means the one on the server
(we're not interested in this file on the client side).
* the server also has a `~/.gitolite/keydir` which contains a bunch of
`*.pub` files.
#### normal gitolite key handling
Here's how normal gitolite key handling works:
* (on client) pub key changes like adding new ones, deleting old ones, etc.,
are done in the `keydir` directory in the gitolite-admin repo clone. Then
the admin `git add`s and `git commit`s those changes, then `git push`es
them to the server.
* (on server) a successful push from the client makes git invoke the
post-update hook in the gitolite-admin repo. This hook is installed by
gitolite, and it does a bunch of things which are quite transparent to
the admin, but we'll describe briefly here:
* the pubkey files from this push are checked-out into
`~/.gitolite/keydir` (and similarly the config files into
`~/.gitolite/conf`)
* the "compile" script then runs, which uses these files to populate
`~/.ssh/authorized_keys` on the server
The authkeys file may have other, (non-gitolite) keys also. Those
lines are preserved. Gitolite only touches lines that are found
between gitolite's "marker" lines (`# gitolite start` and `# gitolite
end`).
### (Other resources)
People who think installing gitolite is too hard should take a look at this
[tutorial][tut] to **see how simple it *actually* is**.
### common ssh problems
Since I'm pretty sure at least some of you didn't bother to read the
"IMPORTANT: PLEASE READ FIRST" section above, let me take a minute to point
you there again. Especially the first bullet.
Done? OK, read on...
The following problem(s) indicate that pubkey access is not working at all, so
you should start with [appendix 1][stsapp1_]. If that doesn't fix the problem, continue
with the other appendices in sequence.
* running any git clone/fetch/ls-remote or just `ssh git@server info` asks
you for a password.
The following problem(s) indicate that your pubkey is bypassing gitolite and
going straight to a shell. You should start with [appendix 2][stsapp2_] and continue with
the rest in sequence. [Appendix 5][stsapp5_] has some background info.
* running `ssh git@server info` gets you the output of the GNU 'info'
command instead of gitolite's version and access info.
* running `git clone git@server:repositories/reponame` (note presence of
`repositories/` in URL) works.
[A proper gitolite key will only let you `git clone git@server:reponame`
(note absence of `repositories/`)]
* you are able to clone repositories but are unable to push changes back
(the error complains about the `GL_RC` environment variable not being set,
and the `hooks/update` failing in some way).
[If you run `git remote -v` you will find that your clone URL included the
`repositories/` described above!]
* conversely, using the correct syntax, `git clone git@server:reponame`
(note absence of `repositories/` in the URL), gets you `fatal: 'reponame'
does not appear to be a git repository`, and yet you are sure 'reponame'
exists, you haven't mis-spelled it, etc.
### step by step
Since I'm pretty sure at least some of you didn't bother to read the
"IMPORTANT: PLEASE READ FIRST" section above, let me take a minute to point
you there again. Especially the first bullet.
Done? OK, now the general outline for ssh troubleshooting is this:
* make sure the server's overall setup even *allows* pubkey based login.
I.e., check that git fetch/clone/ls-remote commands or a plain `ssh
git@server info` do NOT ask for a password. If you do get asked for a
password, see [appendix 1][stsapp1_].
* match client-side pubkeys (`~/.ssh/*.pub`) with the server's authkeys
file. To do this, run `sshkeys-lint`, which tells you in detail what key
has what access. See [appendix 2][stsapp2_].
* at this point, we know that we have the right key, and that if sshd
receives that key, things will work. But we're not done yet. We still
need to make sure that this specific key is being offered/sent by the
client, instead of the default key. See [appendix 3][stsapp3_] and [appendix 4][sshhostaliases].
### random tips, tricks, and notes
#### giving shell access to gitolite users
We've managed (thanks to an idea from Jesse Keating) to make it possible for a
single key to allow both gitolite access *and* shell access.
This is done by copying the pubkey (to which you want to give shell access) to
the server and running
gl-tool add-shell-user ~/foo.pub
**IMPORTANT UPGRADE NOTE**: previous implementations of this feature were
crap. There was no easy/elegant way to ensure that someone who had repo admin
access would not manage to get himself shell access.
Giving someone shell access requires that you should have shell access in the
first place, so the simplest way is to enable it from the server side only.
#### losing your admin key
If you lost the admin key, and need to re-establish ownership of the
gitolite-admin repository with a fresh key, get a shell on the server and use
the program called `gl-admin-push` that comes with gitolite. See instructions
[here][adminpush].
#### simulating ssh-copy-id
don't have `ssh-copy-id`? This is broadly what that command does, if you want
to replicate it manually. The input is your pubkey, typically
`~/.ssh/id_rsa.pub` from your client/workstation.
* it copies it to the server as some file
* it appends that file to `~/.ssh/authorized_keys` on the server
(creating it if it doesn't already exist)
* it then makes sure that all these files/directories have go-w perms
set (assuming user is "git"):
/home/git/.ssh/authorized_keys
/home/git/.ssh
/home/git
[Actually, `sshd` requires that even directories *above* `~` (`/`, `/home`,
typically) also must be `go-w`, but that needs root. And typically
they're already set that way anyway. (Or if they're not, you've got
bigger problems than gitolite install not working!)]
#### problems with using non-openssh public keys
Gitolite accepts public keys only in openssh format. Trying to use an "ssh2"
key (used by proprietary SSH software) results in:
WARNING: a pubkey file can only have one line (key); ignoring YourName.pub
To convert ssh2-compatible keys to openssh run:
ssh-keygen -i -f /tmp/ssh2/YourName.pub > /tmp/openssh/YourName.pub
then use the resulting pubkey as you normally would in gitolite.
#### windows issues
On windows, I have only used msysgit, and the openssh that comes with it.
Over time, I have grown to distrust putty/plink due to the number of people
who seem to have trouble when those beasts are involved (I myself have never
used them for any kind of git access). If you have unusual ssh problems that
just don't seem to have any explanation, try removing all traces of
putty/plink, including environment variables, etc., and then try again.
Thankfully, someone contributed [contrib/putty.mkd][contrib_putty].
### #stsapp1_ appendix 1: ssh daemon asks for a password
> **NOTE**: This section should be useful to anyone trying to get
> password-less access working. It is not necessarily specific to gitolite,
> so keep that in mind if the wording feels a little more general than you
> were expecting.
You have generated a keypair on your workstation (`ssh-keygen`) and copied the
public part of it (`~/.ssh/id_rsa.pub`, by default) to the server.
On the server you have appended this file to `~/.ssh/authorized_keys`. Or you
ran something, like the `gl-setup` step during a gitolite install, which
should have done that for you.
You now expect to log in without having to type in a password, but when you
try, you are being asked for a password.
This is a quick checklist:
* make sure you're being asked for a password and not a pass*phrase*. Do
not confuse or mistake a prompt saying `Enter passphrase for key
'/home/sitaram/.ssh/id_rsa':` for a password prompt from the remote
server!
When you create an ssh keypair using `ssh-keygen`, you have the option of
protecting it with a passphrase. When you subsequently use that keypair
to access a remote host, your *local* ssh client needs to unlock the
corresponding private key, and ssh will probably ask for the passphrase
you set when you created the keypair.
You have two choices to avoid this prompt every time you try to use the
private key. The first is to create keypairs *without* a passphrase (just
hit enter when prompted for one). **Be sure to add a passphrase later,
once everything is working, using `ssh-keygen -p`**.
The second is to use `ssh-agent` (or `keychain`, which in turn uses
`ssh-agent`) or something like that to manage your keys. Other than
discussing one more potential trouble-spot with ssh-agent (see below),
further discussion of ssh-agent/keychain is out of scope of this document.
* ssh is very sensitive to permissions. An extremely conservative setup is
given below, but be sure to do this on **both the client and the server**:
cd $HOME
chmod go-rwx .
chmod -R go-rwx .ssh
* actually, every component of the path to `~/.ssh/authorized_keys` all the
way upto the root directory must be at least `chmod go-w`. So be sure to
check `/` and `/home` also.
* while you're doing this, make sure the owner and group info for each of
these components are correct. `ls -ald ~ ~/.ssh ~/.ssh/authorized_keys`
will tell you what they are.
* you may also want to check `/etc/ssh/sshd_config` to see if the "git" user
is allowed to login at all. For example, if that file contains an
`AllowUsers` config entry, then only users mentioned in that line are
allowed to log in!
* some OSs/distributions require that the "git" user should have a password
and/or not be a locked account. You may want to check that as well.
* if all that fails, log onto the server as root, `cd /var/log`, and look
for a file called `auth.log` or `secure` or some such name. Look inside
this file for messages matching the approximate time of your last attempt
to login, to see if they tell you what is the problem.
### #stsapp2_ appendix 2: which key is which -- running sshkeys-lint
Follow these steps on the client:
* get a copy of `~/.ssh/authorized_keys` from the server and put it in
`/tmp/foo` or something
* cd to `~/.ssh`
* run `sshkeys-lint *.pub < /tmp/foo`
This tells you, for each pubkey, what type of access (if any) it has to the
server.
Note that it is not trying to log in or anything -- it's just comparing bits
of text (the contents of STDIN taken as an authkeys file, and the contents of
each of the `*.pub` files one by one).
> Note: It's also a stand-alone program, so even if your gitolite version is
> old, you can safely bring over just this program from a more recent
> gitolite and use it, without having to upgrade gitolite itself.
If the pubkey file you're interested in appears to have the correct access to
the server, you're done with this step.
Otherwise you have to rename some keypairs and try again to get the effect you
need. Be careful:
* do not just rename the ".pub" file; you will have to rename the
corresponding private key also (the one with the same basename but without
an extension)
* if you're running ssh-agent, you may have to delete (using `ssh-add -D`)
and re-add identities for it to pick up the renamed ones correctly
#### typical cause(s)
The admin often has passwordless shell access to `git@server` already, and
then used that same key to get access to gitolite (i.e., copied that same
pubkey as YourName.pub and ran `gl-setup` on it).
As a result, the same key appears twice in the authkeys file now, and since
the ssh server will always use the first match, the second occurrence (which
invokes gitolite) is ignored.
To fix this, you have to use a different keypair for gitolite access. The
best way to do this is to create a new keypair, copy the pubkey to the server
as YourName.pub, then run `gl-setup YourName.pub` on the server. Remember to
adjust your agent identities using ssh-add -D and ssh-add if you're using
ssh-agent, otherwise these new keys may not work.
### #stsapp3_ appendix 3: ssh client may not be offering the right key
* make sure the right private key is being offered. Run ssh in very
verbose mode and look for the word "Offering", like so:
ssh -vvv user@host pwd 2> >(grep -i offer)
If some keys *are* being offered, but not the key that was supposed to be
used, you may be using ssh-agent (next bullet). You may also need to
create some host aliases in `~/.ssh/config` ([appendix 4][sshhostaliases]).
* (ssh-agent issues) If `ssh-add -l` responds with either "The agent has no
identities." or "Could not open a connection to your authentication
agent.", then you can skip this bullet.
However, if `ssh-add -l` lists *any* keys at all, then something weird
happens. Due to a quirk in ssh-agent, ssh will now *only* use one of
those keys, *even if you explicitly ask* for some other key to be used.
In that case, add the key you want using `ssh-add ~/.ssh/YourName` and try
the access again.
### F=sshhostaliases appendix 4: host aliases
(or "making git use the right options for ssh")
The ssh command has several options for non-default items to be specified.
Two common examples are `-p` for the port number if it is not 22, and `-i` for
the public key file if you do not want to use just `~/.ssh/id_rsa` or such.
Git has two ssh-based URL syntaxes, but neither allows specifying a
non-default public key file. And a port number is only allowed in one of
them. (See `man git-clone` for details). Finally, hosts often have to be
referred with IP addresses (such is life), or the name is very long, or hard
to remember.
Using a "host" para in `~/.ssh/config` lets you nicely encapsulate all this
within ssh and give it a short, easy-to-remember, name. Example:
host gitolite
user git
hostname a.long.server.name.or.annoying.IP.address
port 22
identityfile ~/.ssh/id_rsa
Now you can simply use the one word `gitolite` (which is the host alias we
defined here) and ssh will infer all those details defined under it -- just
say `ssh gitolite` and `git clone gitolite:reponame` and things will work.
(By the way, the 'port' and 'identityfile' lines are needed only if you have
non-default values, although I put them in anyway just to be complete).
If you have *more than one* pubkey with access to the *same* server, you
**must** use this method to make git pick up the right key. There is no other
way to do this, as far as I know.
[tut]: http://sites.google.com/site/senawario/home/gitolite-tutorial
### #stsapp5_ appendix 5: why bypassing gitolite causes a problem
When you bypass gitolite, you end up running your normal shell instead of the
special gitolite entry point script `gl-auth-command`.
This means commands (like 'info') are interpreted by the shell instead of
gitolite.
It also means git operations look for repos in `$HOME`.
However, gitolite places all your repos in a subdirectory pointed to by
`$REPO_BASE` in the rc file (default: `repositories`), and internally prefixes
this before calling the actual git command you invoked. Thus, the pathname of
the repo that you use on the client is almost never the correct pathname on
the server. (This is by design. Don't argue...)
This means that, you get 2 kinds of errors if you bypass gitolite
* when you use `git@server:reponame` with a key that bypasses gitolite
(i.e., gets you a shell), this prefixing does not happen, and so the repo
is not found. Neither a clone/fetch nor a push will work.
* conversely, consider `git@server:repositories/reponame.git`. The clone
operation will work -- you're using the full Unix path, (assuming default
`$REPO_BASE` setting), and so the shell finds the repo where you said it
would be. However, when you push, gitolite's **update hook** kicks in,
and fails to run because some of the environment variables it is expecting
are not present.

View File

@ -1,11 +0,0 @@
# ssh
There are two documents you need to read, in order:
* [gitolite and ssh][glssh] -- this explains how gitolite uses openssh's
features to provide any number of virtual users over just one actual
(unix) user, and so on
* [ssh troubleshooting][sts] -- this is a rather long document but as far
as I know almost every known ssh related issue is in here. If you find
something missing, send me an email with details.

View File

@ -1,6 +0,0 @@
# unique setups
This page is for unique setups that I support. At present there is only one
-- Fedora.

View File

@ -1,10 +0,0 @@
## #g2alt alternate implementations for some g2 features
The following g2 features have been dropped but suitable (or better)
alternatives exist.
### gl-time for performance measurement
Take a look at the 'cpu-time' program that you can set to run from the
`POST_GIT` trigger. Just set it to run as the last program in that sequence
so it covers all previous programs.

View File

@ -1,4 +0,0 @@
## #g2dropped g2 features dropped
(none yet that are not already covered in the rc section in [this][g2migr]
page).

View File

@ -1,41 +0,0 @@
## #g2incompat incompatibility with g2
(other than in the rc file, which is dealt with [elsewhere][g2rcdiff])
The following incompatibilities exist, in vaguely decreasing order of
severity. **The ones in the first section are IMPORTANT because they allow
access that was previously not allowed -- please fix your config before using
the new gitolite!**
### fallthru in NAME rules
Fallthru on all VREFs is "success" now, so any NAME/ rules you have **MUST**
change the ruleset in some way to maintain the same restrictions. The
simplest is to add the following line to the end of each repo's rule list:
- NAME/ = @all
### subconf command in admin repo
(This is also affected by the previous issue, 'fallthru in NAME rules'; please
read that as well).
If you're using delegation in your admin conf setup, please add the following
lines to the end of the gitolite-admin rules in your conf/gitolite.conf file:
repo gitolite-admin
- NAME/ = @all
subconf "fragments/*.conf"
The first part compensates for fallthru now being a success when processing
[VREF][vref] rules (NAME rules are just one specific VREF). Although,
**ideally**, you should change your ruleset so that you no longer require that
line. As the [vref documentation][vref] says:
> **Virtual refs are best used as additional "deny" rules**, performing
> extra checks that core gitolite cannot.
The second part explicitly says when and where to include the subconf files.
(Before subconf was invented, this used to happen implicitly at the end of the
main conf file, and was hardcoded to that specific glob.)

View File

@ -1,107 +0,0 @@
## #g2migr migrating from g2
<font color="red">
**This document is a *MUST* read if you are currently using g2 and want to
move to g3.**
</font>
First things first: g2 will be supported for a good long time. My current
*expert* users do not cause me any load anyway.
Migration should be straightforward, but it is not automatic. You should run
the "check-g2-compat" program first, to see any *major* differences that
affect you. The bulk of the changes are in the RC file, which must be
manually handled (links below). The conf files have very few changes -- they
apply only if you use "NAME/" or delegation.
You must first read about [incompatible][g2incompat] features and
[dropped][g2dropped] features. Some features have been replaced with
[alternatives][g2alt].
Since the majority of changes are in the rc file, they're all listed
[here][g2rcdiff].
The rest of this page describes the completely standalone "check-g2-compat"
script that you can find in the repo root (i.e., not in "src/").
### the "check-g2-compat" program
This program checks a few things only, not everything. In particular, it
looks for settings and status that might:
* make g3 unusable for lots of users
* make g3 give *more* access than g2 under some conditions.
It does NOT look for or warn about anything else; you're expected to read (and
act upon, if needed) the rest of the migration guide links given a few paras
above to cover everything else.
Here's an explanation of those messages that the check-g2-compat program may
put that contain the words "see docs":
* `GL_ADMINDIR in the wrong place -- aborting`
It expects to find `GL_ADMINDIR` and `REPO_BASE` pointing to the right
places. It aborts if these conditions are not met and does not scan
further since that sort of guesswork is not good. If you are in that
position, make a symlink from the real location to the expected location,
change the RC accordingly, and re-try.
* `REPO_BASE in the wrong place -- aborting`
same as above
* `fallthru in NAME rules`
**This is a significant difference and affects access badly (gives access
that would otherwise not be given)**. Please see the [list of non-RC
incompatibilities][g2incompat].
* `subconf command in admin repo`
This is not so bad security wise but it might *reduce* access by not
processing files you intended to. Again, see the same link as in the
previous bullet.
* `found N gl-creater files`
These need to be renamed to `gl-creator` (the correct spelling at last,
hooray!). Suggested command sequence:
cd $HOME/repositories
find . -type d -name "*.git" -prune | while read r
do
mv $r/gl-creater $r/gl-creator
done 2>/dev/null
Once you do this, the g2 will not work completely unless you change them
back.
* `found N gl-perms files with R or RW`
Setting perms of R and RW will no longer work; you have to say READERS and
WRITERS now. Suggested command:
The following variables need to be [preset][rc-preset] in the rc file
**before** running `gitolite setup`. Otherwise the default actions will
clobber something and require some recovery.
* `GL_NO_SETUP_AUTHKEYS` (default will clobber your authkeys file)
* `GL_NO_DAEMON_NO_GITWEB` (default will clobber your projects.list file and
git-daemon-export-ok files)
* `UPDATE_CHAINS_TO` (default will fail to run this extra check when users
push)
* `ADMIN_POST_UPDATE_CHAINS_TO` (severity depends on what your code is
doing; see [g2rcdiff][] for how to fix this)
* `GL_ALL_INCLUDES_SPECIAL` (default will allow gitweb and daemon to be able
to read any repos that have `R = @all`)
* `GIT_PATH` (presumably your git is in some non-std path so unless you
preset `$ENV{PATH}` per instructions in the [rc file
differences][g2rcdiff] doc, nothing will work).

View File

@ -1,125 +0,0 @@
## #g2rcdiff rc file differences between g2 and g3
The new rc file has far fewer variables; many have been dropped. You should
not see much ill effect though, but please read below.
### #rc-preset pre-setting the rc file
Some of these settings are such that you cannot directly run `gitolite setup`
when you're ready to migrate. Instead, you need to run
# (assuming you saved your g2 rc file somewhere)
gitolite print-default-rc > $HOME/.gitolite.rc
$EDITOR $HOME/.gitolite.rc
# make appropriate changes (see below), save
gitolite setup
The most serious example of this is `GL_NO_SETUP_AUTHKEYS`. If you don't
preset the rc (in this case, by commenting out the 'ssh-authkeys' line)
**before** running `gitolite setup`, your `~/.ssh/authorized_keys` file will
get clobbered.
There are several other settings that you may want to look at, with varying
degrees of severity; **please go through the list below before blindly running
`gitolite setup`**. (For example, if you had `GL_NO_DAEMON_NO_GITWEB` in g2
but forgot to comment out all the gitweb and daemon update lines in the rc
file, your projects.list and git-daemon-export-ok files might get clobbered.
Of course this is less severe than ssh authkeys but still...).
### rc file differences
**DROPPED** variables (possible high impact): these could be show-stoppers for
migration, at least for now.
* `BIG_INFO_CAP` -- if you think you must have this, try it without and see
if there's a difference. If you *know* you need this, convince me.
* `GL_ALL_READ_ALL` -- same
* `GL_NO_CREATE_REPOS` -- if you think you need this, email me. I know one
group who does need this so I will be putting it in eventually but not
right away.
* `HTPASSWD_FILE`, `RSYNC_BASE`, `SVNSERVE` -- need work. Email me if you
are using any of these.
* `GL_GET_MEMBERSHIPS_PGM` -- is high on my todo list
* `GL_LOGT` -- is now fixed; you can't change it. Email me if this is a
problem.
**DROPPED** variables (medium impact): these have alternative implementations
or mechanisms, but you have to do some setup work.
* `GL_ADMINDIR` -- this is now at a fixed location: `~/.gitolite`. If you
want it somewhere else go ahead and move it, then place a symlink from the
assumed location to the real one.
* `REPO_BASE` -- this is now at a fixed location: `~/repositories`. If you
want it somewhere else go ahead and move it, then place a symlink from the
assumed location to the real one.
* `PROJECTS_LIST` -- it's called `GITWEB_PROJECTS_LIST` now, but more
importantly, it is only used by update-gitweb-access-list in
src/commands/post-compile. This variable now has nothing to do with
gitolite core, and the rc is just helping to store settings for external
programs like that one.
`WEB_INTERFACE` and `GITWEB_URI_ESCAPE` are also gone; patches to the
update program to directly do those things are welcome. Personally, I
think people who use spaces and other funky characters in dir/file names
should be shot but luckily no one listens to me :-)
* `GL_NO_DAEMON_NO_GITWEB` -- uncomment the appropriate lines in the rc
file, in both the `POST_COMPILE` and `POST_CREATE` trigger sections.
* `NICE_VALUE` -- uncomment the 'renice 10' line in the rc file. You can
also change the 10 to something else if you wish.
* `GIT_PATH` -- gone, not needed. Just add these lines to the end of the rc
file:
$ENV{PATH}="...whatever you want...";
1;
* `GL_NO_SETUP_AUTHKEYS` -- comment out the lines that call ssh-authkeys, in
the rc file.
* `GL_WILDREPOS_DEFPERMS` -- if you need this, add a `POST_CREATE` script
that does it. Or email me and I will write it for you.
* `UPDATE_CHAINS_TO` -- use a [vref][] instead. You can directly use the
chained-to script as a VREF; it'll work.
* `ADMIN_POST_UPDATE_CHAINS_TO` -- add your script to the `POST_COMPILE`
trigger chain. You won't be getting any arguments but for the admin repo
the only argument that ever comes in is "refs/heads/master" anyway.
* `GL_ADC_PATH` -- obsolete; use [commands][] or add [your own][dev-notes].
* `GL_ALL_INCLUDES_SPECIAL` -- obsolete; @all always includes gitweb and
daemon now. Use [deny-rules][] if you want to say `R = @all` but not have
it be visible to gitweb or daemon.
* `GL_PERFLOGT` -- see the entry for "gl-time" in the [alternative
implementations][g2alt] page.
**DROPPED** variables (no impact/low impact): these variables should not
actually affect anything anyway, so even if you had them set you should not
feel their loss.
* `GL_CONF`, `GL_KEYDIR`, and `GL_CONF_COMPILED` -- you had no business
touching these anyway; if you did, move them into the expected default
locations before attempting to run `gitolite setup`
* `GL_PACKAGE_HOOKS` -- not needed anymore, but check if you had any custom
hooks set there and copy them across.
* `GL_WILDREPOS` -- dropped; this feature is default now.
* `GL_BIG_CONFIG` -- dropped; this feature is default now.
**RENAMED** variables (no impact): these are functionally the same but are
renamed.
* `REPO_UMASK` is now `UMASK`
* `GL_GITCONFIG_KEYS` is now `GITCONFIG_KEYS`
* `GL_WILDREPOS_PERM_CATS` is now the ROLES hash in the rc file
* `GL_SITE_INFO` is now `SITE_INFO`

View File

@ -1,97 +0,0 @@
# why a completely new version?
Gitolite started life as 400 lines of perl written on a weekend because I was
quickly losing control of my projects at work, exacerbated by git newbies
doing all the wrong things. I really needed it!
That little 400 line thing is now a huge bunch of programs that do all sorts
of things (I mean, rsync access control in a git related program? WTF!),
because it kinda just *grew* while I wasn't looking.
So, briefly, here are the advantages of g3:
* compile versus everything else
g2's "compile" script was doing way, way too much. For example, dealing
with gitweb and git-daemon was a good chunk of code in g2. In contrast,
here's how g3 generates gitweb's projects.list file:
(
gitolite list-phy-repos | gitolite access % gitweb R any | grep -v DENIED
gitolite list-phy-repos | gitolite git-config -r % gitweb\\.
) |
cut -f1 | sort -u | sed -e 's/$/.git/' > $plf
* core versus non-core
That's just the tip of the iceberg. The commands above run from a script
that is itself outside gitolite, and can be enabled and disabled from the
rc file. There are six different "events" within gitolite that can
trigger external programs, with specific arguments passed to them, much
like git's own hooks. The example you saw is called from the
"POST_COMPILE" trigger.
And as you can see, these programs be in any language.
* get/set perms/desc, and ADCs
I've always wanted to kick setperms out of core and make it an ADC.
Sadly, it couldn't be done because when you update your repo's permissions
using setperms, that can affect gitweb/daemon access, which -- you guessed
right -- feeds back into the main code in complex ways. It *had* to be an
"inside job".
But now, the new 'perms' program is quite external to gitolite. And how
does it fix up gitweb/daemon permissions after it is done updating the
"gl-perms" file?
system("gitolite", "trigger", "POST_CREATE");
* syntax versus semantics
I got tired of people asking things like "why can't I have
backslash-escaped continuation lines?" I designed it differently because
I don't like them but perhaps it's reasonable for some people.
Someone else wanted to use subdirectories of 'keydir' as group names. Why
not?
G3 comes with a stackable set of "syntactic sugar" helpers. And you can
write your own, though they do have to be in perl (because they're not
standalone programs).
Once the code is written and placed in the right place, all a site has to
do to enable it is to uncomment some lines in the rc file:
# these will run in sequence during the conf file parse
SYNTACTIC_SUGAR =>
[
# 'continuation-lines',
# 'keysubdirs-as-groups',
<etc>
* roll your own
Having a decent shell API helps enormously. You saw an example above but
how about if your boss asks you "I need a list of everyone who *currently*
has read access to the 'foo' repo"?
Sure you could look in conf/gitolite.conf, all its include files (if you
have any), and if the repo is user-created, then in its gl-perms.
Or you could do something like this:
gitolite list-users | gitolite access foo % R any | cut -f1
* over-engineered
g2 was, to some extent, over-engineered. One of the best examples is the
documentation on hook-propagation in g2, which required even a *picture*
to make clear (always a bad sign). In g3, the [hooks][] section is 4
sentences.
Anyway you get the idea.
The point is not that you can do all these cool tricks. The point is they are
possible because of the redesign. There is no way on God's green earth I
could have done this with the old code.

View File

@ -1,32 +0,0 @@
# parts of the conf file
## #group group definitions
You can group repos or users for convenience. The syntax is the same for both
and does not distinguish; until you *use* the group name it could really be
either.
Here's an example:
@developers = dilbert alice wally
Group definitions accumulate; this is the same as the above:
@developers = dilbert
@developers = alice
@developers = wally
You can use one group in another group definition; the values will be expanded
right there (meaning later additions will not appear in the second group).
@developers = dilbert alice
@interns = ashok
@staff = @interns @developers
@developers = wally
# wally is NOT part of @staff
### special group `@all`
`@all` is a special group name that is often convenient to use if you really
mean "all repos" or "all users".

View File

@ -1,67 +0,0 @@
# Hosting git repositories
Gitolite allows you to setup git hosting on a central server, with
fine-grained access control and many more powerful features.
Here's more on [what][] it is and [why][] you might need it.
For current gitolite (call it "g2" for convenience) users,
* [Why][g3why] I rewrote gitolite.
* Development [status][dev-status] (**should change often for a while**)
* Specific migration [issues and steps][g2migr].
Quick links:
* [Minimum requirements][minreq].
* Here's how to [get started][qi] installing and setting it up
* Don't know ssh well enough? [Learn][ssh]. It's **IMPORTANT**.
* [Add users and repos][add].
* Learn about fine-grained access control with the [conf][] file
* Explain gitolite to your [users][].
Not so "quick" links:
* The "master table of contents" link at the top of each page is the
**first** place you should check when looking for anything.
* Want to do more than just add users and repos and control their access?
Here's how to [customise][cust] your server installation.
Additional reading for Unix newbies:
* [Regular Expressions][regex]
## #what What is gitolite?
Gitolite is an access control layer on top of git. Here are the features that
most people see:
* use a single unix user ("real" user) on the server
* provide access to many gitolite users
* they are not "real" users
* they do not get shell access
* control access to many git repositories
* read access controlled at the repo level
* write access controlled at the branch/tag/file/directory level,
including who can rewind, create, and delete branches/tags
* can be installed without root access, assuming git and perl are already
installed
* authentication is most commonly done using sshd, but you can also use
httpd if you prefer (this may require root access).
## #contact contact
* author: sitaramc@gmail.com, sitaram@atc.tcs.com
* mailing list: gitolite@googlegroups.com
* list subscribe address : gitolite+subscribe@googlegroups.com
* IRC: #git and #gitolite on freenode. Note that I live in India (UTC+0530
time zone).
## #license license
The gitolite *code* is released under GPL v2. See COPYING for details.
The gitolite documentation is provided under a [Creative Commons
Attribution-NonCommercial-ShareAlike 3.0 Unported
License](http://creativecommons.org/licenses/by-nc-sa/3.0/).

View File

@ -1,63 +0,0 @@
# different ways to install gitolite
Gitolite has only one server side "command" now, much like git itself. And
it's been designed so that you don't even really have to *install* it, as you
will see.
**NOTE**: if you're migrating from g2, there are some settings that MUST be
dealt with **before** running `gitolite setup`; please read the [g2
migration][g2migr] page and linked pages, and especially the one on
'presetting the rc file][rc-preset].
## simplest
1. Put all of `src` in one place, doesn't matter where; let's call it
/foo/bar.
2. Use the full path to run any gitolite commands, for example:
/foo/bar/gitolite setup -pk sitaram.pub
## almost as simple
1. (same as above)
2. Symlink /foo/bar/gitolite to some directory that is on your PATH. For
example:
ln -sf /foo/bar/gitolite ~/bin
Now you can just say
gitolite setup -pk sitaram.pub
## packagers
1. Put src/Gitolite in `/usr/share/perl5/vendor_perl` or some such place.
2. Put the rest of src anywhere your distro policy allows. (Fedora keeps
git's 150 executables in /usr/libexec/git-core, so maybe
/usr/libexec/gitolite?)
3. Symlink 'gitolite' to /usr/bin or something, similar to step 2 above,
OR
Put it directly in /usr/bin, and hardcode `GL_BINDIR` into it to tell it
where the others are. I'd prefer it if you did not do this but you can.
----
Bottom line:
* `GL_BINDIR` must point to a place that contains `commands`, `VREF`, and
`syntactic-sugar` (so they must all be sibling directories).
* The `Gitolite` directory can also be there, or it can be anywhere in
perl's `@INC` path.
## upgrading
Just put the new version on top of wherever you kept the old one. That's it.
If you feel it should require a little more effort, pretend I said "you have
to then run `gitolite setup`". Can't hurt...

View File

@ -1,45 +0,0 @@
index.mkd
why.mkd
g3why.mkd
dev-status.mkd
g2migr.mkd
g2rcdiff.mkd
g2incompat.mkd
g2dropped.mkd
g2alt.mkd
minreq.mkd
qi.mkd
install.mkd
add.mkd
users.mkd
conf.mkd
group.mkd
repo.mkd
rules.mkd
refex.mkd
write-types.mkd
rc.mkd
cust.mkd
triggers.mkd
vref.mkd
non-core.mkd
dev-notes.mkd
misc.mkd
pw.mkd
testing.mkd
extras/auth.mkd
extras/nagp.mkd
extras/regex.mkd
extras/ssh.mkd
extras/gitolite-and-ssh.mkd
extras/ssh-troubleshooting.mkd

View File

@ -1,32 +0,0 @@
# minimum requirements for gitolite
**Client**:
* git 1.6.6 or greater
* an ssh client that can talk to an openssh server, and can generate keys in
openssh's default format (the pubkey is just one long line). Gitolite
will [not currently][pw1] convert such keys.
For people still using Windows, msysgit works fine. If you're using
[putty/plink][ens], God bless you. (It'll work, but I still want him to bless
you).
TODO: when smart http support works, ssh will no longer be a *requirement*,
merely a *strong* suggestion :-)
**Server**
* git 1.6.6 or greater
* perl 5.8.8 or greater
* an ssh server compatible with openssh, especially it's authorized keys
file format and features.
* any Unix or Unix like OS. That said, I've occasionally had some weird
reports from [Mac OSX servers][ens]; good luck.
* a single, dedicated, userid to host it (usually 'git' or 'gitolite').
These version numbers are subject to fine-tuning as I get feedback and make
fixes where possible and needed.
Sshd must be configured so that each users authkeys file is in the user's
`$HOME`, inside `.ssh/authorized_keys`, and not in some central
/var/something.

View File

@ -1,132 +0,0 @@
# odds and ends
Most of these items don't fit anywhere or fit in more than one place or are of
the nature of background information.
## #include include files
Gitolite allows you to break up the configuration into multiple files and
include them in the main file for convenience.
include "foo.conf"
will include the contents of the file "foo.conf" from the "conf" directory.
Details:
* You can also use a glob (`include "*.conf"`), or put your include files
into subdirectories of "conf" (`include "foo/bar.conf"`), or both
(`include "repos/*.conf"`).
* Included files are always searched relative to the gitolite-admin repo's
"conf/" directory.
* If you ended up recursing, files that have been already processed once are
skipped, with a warning.
<font color="gray">Advanced users: `subconf`, a command that is very closely
related to `include`, is documented [here][subconf].</font>
## #deny-rules applying deny rules at the pre-git access check
The access [rules][] rules section describes the problem. To recap, you want
this:
@staff = alice bob wally ashok
repo foo
RW+ = alice # line 1
RW+ dev = bob # line 2
- = wally # line 3
RW temp/ = @staff # line 4
to deny Wally even *read* access.
The way to do this is to add this line to the repo:
option deny-rules = 1
If you want this for all your repos, just add this somewhere at the top of
your conf file
repo @all
option deny-rules = 1
## #rule-accum rule accumulation
Gitolite was meant to collect rules from multiple places and apply them all.
For example, this:
repo foo
RW = u1
@gr1 = foo bar
repo @gr1
RW = u2
R = u3
repo @all
R = gitweb
is effectively the same as this, for repo foo:
repo foo
RW = u1
RW = u2
R = u3
R = gitweb
This extends to patterns also, but I'll leave an example for later.
## #subconf the subconf command
This is just like the include command:
subconf "foo/bar.conf" # example 1
subconf "foo/*.conf" # example 2
with the difference that, for the duration of the file(s) being included, a
subconf restriction is in effect. This restrictions limits the repos that can
be access controlled by the lines within the included file(s).
Here's how it works. First, a subconf *name* is derived from the filename
being included, which is basically the basename of the file. For example 1
that is "bar". For example 2, assuming foo contains "a.conf" and "b.conf",
the subconf name is "a" while processing "a.conf", and "b" while processing
"b.conf".
A variation of the subconf command allows you to specify the subconf *name*
explicitly, while including files as before:
subconf frob "foo/bar.conf # example 3
subconf frob "foo/*.conf # example 4
In this case the subconf name is "frob" in both cases.
A subconf restricts the repos that can be named in 'repo' lines while the
subconf is in effect. If the subconf name is "foo", the conf lines parsed
while under the subconf restriction can only refer to
* a repo called 'foo'
* a repo group called '@foo'
* the members of the same repo group '@foo'
* match a regex that is a member of the repo group '@foo'
In the last 3 cases, the repo group '@foo' must be defined in the main conf
file (i.e., outside the subconf restriction).
For example, if you have this in the main conf file:
@foo = bar baz frob/..*
subconf "foo.conf"
then foo.conf can only refer to 'foo', '@foo', 'bar', 'baz', or any repo name
matching the pattern `frob/..*`.
## #HOSTNAME HOSTNAME substitution
Wherever gitolite sees the word `%HOSTNAME`, it will replace it with the
HOSTNAME supplied in the rc file, if one was supplied. This is mainly useful
in [mirroring][].

115
doc/mkdoc
View File

@ -1,115 +0,0 @@
#!/usr/bin/perl
# cd <g3 clone>/doc; ./mkdoc # --> creates ../html/*.html
my $MKD = "./Markdown.pl";
use 5.10.0;
use strict;
use warnings;
use lib '../src/Gitolite/Test';
use Tsh;
$ENV{TSH_ERREXIT} = 1;
try "
mkdir ../html; ok
git status -s -uno; !/./
git log --oneline -1
" or die 1;
my $head = (lines())[0];
main();
try "
git checkout gh-pages; ok
git reset --hard github/gh-pages; ok
cd ..; ok
git rm g3/*.html; ok
mkdir g3; ok
mv html/*.html g3; ok
git add g3; ok
git commit -m '$head'; ok
git checkout g3; ok
rmdir html; ok
" or die 2;
sub main {
chomp(@ARGV = `cat list`) if not @ARGV;
@ARGV = grep { $_ ne 'master-toc.mkd' and /./ } @ARGV;
my @save = @ARGV;
my $css = join("", <DATA>);
my $mt = "# gitolite master table of contents/index\n";
my $mf = '';
my $fh;
while (<>) {
$ARGV =~ /^(?:.*\/)?([^\/]+)\.mkd$/;
my $b = $1;
if (/^(#+) (?:#(\S+) )?(.*)/) {
if ( length($1) == 1 ) {
$mt .= "\n";
$mt .= " * [$3][$b]\n";
$mf .= "[$b]: $b.html\n";
} else {
$mt .= " " x ( 4 * ( length($1) - 1 ) );
$mt .= " * ";
$mt .= (
$2
? "[$3][$2]"
: "$3"
);
$mt .= "\n";
$mf .= "[$2]: $b.html" . ($2 ne $b ? "#$2" : "") . "\n" if $2;
}
}
}
open($fh, ">", "master-toc.mkd")
and print $fh $mt
and close $fh;
# after this, do this for every mkd (including the master-toc.mkd)
# cat $css_block > $base.html
# cat $base.mkd $mf | $MKD >> $base.html
for my $mkd ("master-toc.mkd", @save) {
$mkd =~ /^(?:.*\/)?([^\/]+)\.mkd$/;
my $b = $1;
open($fh, ">", "../html/$b.html")
and print $fh $css
and close $fh;
my $mkt = `cat $mkd`;
$mkt =~ s/^(#+) #(\S+) /$1 <a name="$2"><\/a> /mg;
open($fh, "|-", "$MKD >> ../html/$b.html")
and print $fh $mkt, $mf
and close $fh;
}
}
__DATA__
<head><style>
body { background: #fff; margin-left: 40px; font-size: 0.9em; font-family: sans-serif; max-width: 800px; }
h1 { background: #ffb; margin-left: -30px; border-top: 5px solid #ccc; }
h2 { background: #ffb; margin-left: -20px; border-top: 3px solid #ddd; }
h3 { background: #ffb; margin-left: -10px; }
h4 { background: #ffb; }
code { font-size: 1.1em; background: #ddf; }
pre { margin-left: 2em; background: #ddf; }
pre code { font-size: 1.1em; background: #ddf; }
</style></head>
<p style="text-align:center">
<a href="master-toc.html">master TOC</a>
|
<a href="index.html">main page</a>
|
<a href="index.html#license">license</a>
</p>

View File

@ -1,160 +0,0 @@
# non-core programs shipped with gitolite
## commands
A list of these commands can be obtained by running `gitolite help` on the
server. A different (and probably much smaller) list can be obtained by a
remote user running `ssh git@host help`.
All the commands will respond to `-h`; please report a bug to me if they
don't.
## syntactic sugar
The following "sugar" programs are available:
* continuation-lines -- allow the use of C-style backslash escaped
continuation lines in the conf file. I don't like it but some people do,
and now I can support them without bulking up the "core" conf parser!
* keysubdirs-as-groups -- someone wanted the sub-directory name (of
"keydir/") in which the pubkey was placed to be a group to which the user
automatically belonged. A very unusual requirement, and one which would
*never* have seen the light of day in g2, but in g3 it's easy, and doesn't
affect anyone else!
(Note: the last component of the directory path is used if there are more
than one level between "keydir/" and the actual file).
## triggers
The `PRE_GIT` triggers are:
* partial-copy -- this has its own section later in this page
* renice -- this renices the entire job to whatever value you specify
The `POST_GIT` triggers are:
* cpu-time -- post-git triggers, if you check the [triggers][] doc, receive
4 CPU time numbers from the main shell program. Treat this code as sample
and do do with them as you please to do with as you please.
The `POST_COMPILE` triggers are:
* post-compile/ssh-authkeys -- takes the pubkeys in keydir and populates
`~/.ssh/authorized_keys`
* post-compile/update-git-configs -- updates individual 'repo.git/config'
files (using the 'git config ...' command) from settings supplied in the
conf file. All sections except 'gitolite-options' are processed. (The
'gitolite-options' section is considered internal to gitolite).
* post-compile/update-git-daemon-access-list -- create/delete
'git-daemon-export-ok' files in each repo based on whether the conf says
'daemon' can read the repo or not
* post-compile/update-gitweb-access-list -- populates the file named in
`GITWEB_PROJECTS_LIST` in the rc file (default: `$HOME/projects.list`)
with the list of repos that gitweb is allowed to access. This could be
more than just "R = gitweb"; any repo that has any config setting with the
section name 'gitweb' (like 'gitweb.owner', 'gitweb.description', etc) is
considered readable by gitweb, so the final list is a union of these two
methods
The `POST_CREATE` triggers are:
* the last 3 in the `POST_COMPILE` list also run from `POST_CREATE`, for
obvious reasons.
## VREFs
You should read about [vref][]s in detail first; this won't make sense
otherwise. For a brief recap, note that there are 2 kinds of VREFs: those
that require arguments and those that behave just like any other `update`
hook.
COUNT is an example of the former (hence the long-ish description). DUPKEYS
and EMAIL-CHECK are both examples of the latter.
* COUNT
The COUNT VREF is used like this:
- VREF/COUNT/9 = @junior-developers
In response, if anyone in the user list pushes a commit series that
changes more than 9 files, a vref of "VREF/COUNT/9" is returned. Gitolite
uses that as a "ref" to match against all the rules, hit the same rule
that invoked it, and deny the request.
If the user did not push more than 9 files, the VREF code returns nothing,
and nothing happens.
COUNT can take one more argument:
- VREF/COUNT/9/NEWFILES = @junior-developers
This is the same as before, but have to be more than 9 *new* files not
just changed files.
* DUPKEYS -- this checks keydir/ for duplicate keys and aborts the push if
it finds any. You should use this only on the gitolite-admin repo.
repo gitolite-admin
- VREF/DUPKEYS = @all
* EMAIL-CHECK -- read the comments in the code for this one. Like DUPKEYS,
it does not take any arguments.
* FILETYPE -- this is sample code for a very site-specific purpose; you'll
have to read the code
* MERGE-CHECK -- this is sample code to illustrate how one of the gitolite
built-in functions *could* have been handled, although there are some
differences
* partial-copy -- this has its own section later in this page
## special cases
### partial-copy
Git (and therefore gitolite) cannot do selective read control -- allowing
someone to read branch A but not branch B. It's the entire repo or nothing.
<font color="gray"> [Side note: Gerrit Code Review can do that, but that is
because they have their own git stack (and their own sshd, and so on) all in
one big Java program. Gerrit is *really* useful if you want code review to be
part of the access control decision] </font>
Gitolite can now help you do this, as follows:
1. enable 'partial-copy' in the `PRE_GIT` section in the rc file.
2. for each repo "foo" which has secret branches that a certain set of
developers (we'll use a group called `@temp-emp` as an example) are not
supposed to see, do this:
repo foo
# rules should allow @temp-emp NO ACCESS
repo foo-partialcopy-1
- secret-branch = @temp-emp
# other rules should ensure ONLY @temp-emp has ANY ACCESS
# NO other user should have access
- VREF/partial-copy = @all
config gitolite.partialCopyOf = foo
And that should be it. **Please test it and let me know if it doesn't work!**
WARNINGS:
* if you change the config to disallow something that used to be allowed,
you should delete the partial repo on the server and then run 'gitolite
compile' to let it build again. See t/partial-copy.t for details.
* not tested with smart http; probabl won't work
* also not tested with mirroring, or with wild card repos.

View File

@ -1,45 +0,0 @@
# patches welcome :-)
These are places where I could use some help, with hints about how you would
go about it. In addition, any of the items in [dev-status][] are up for
grabs, if you wish to jump in. But let me know before you start, and how you
plan to go about it.
## #pw2 rsync
The crux of the old rsync ADC was an access check to a repo whose name starts
with EXTCMD. g2 would treat repos whose names start with EXTCMD as "don't
create this repo". g3 makes no such distinctions, but you can get the same
effect by using a repo group name that does not exist:
repo @rsync-set1
...rules...
and check that in the script. (It might mean adding one more function to
Gitolite::Easy but that's... easy!)
## #pw1 converting non-openssh pubkeys automatically
Gitolite's [triggers][] has can be used to fix this. Here's pseudo-code:
chdir $(gitolite query-rc -n GL_ADMIN_BASE)/keydir
for each *.pub file in this directory and its subdirectories
detect its format and convert it
# just overwrite it; the original is saved in git anyway
Let's say the program is called "convert-non-openssh-keys". You send it to me
and I add it to src/commands/post-compile/. Then I update the default rc file
to look at least like this instead
POST_COMPILE =>
[
# 'post-compile/convert-non-openssh-keys',
'post-compile/ssh-authkeys',
...
...
],
making sure to place it *before* ssh-authkeys, and anyone who needs it can
just uncomment it.
Done!

View File

@ -1,26 +0,0 @@
# getting started
The quickest install, assuming your `$PATH` contains `$HOME/bin`, is:
* get the software
git clone git://github.com/sitaramc/gitolite
# (until this becomes "master")
cd gitolite
git checkout -f g3
* install it
ln -sf $PWD/src/gitolite $HOME/bin
If you don't like that, there are [other install methods][install].
Once the install is done, setup:
gitolite setup -pk your-name.pub
And that's it.
Next steps are usually [adding][add] users and repos and learning about
[access control][conf].

View File

@ -1,20 +0,0 @@
# the "rc" file ($HOME/.gitolite.rc)
The rc file for g3 is quite different from that of g2. It has been designed
to be (a) the only thing unique to your site, for most installations and (b)
easy to extend when new needs show up, without having to touch core gitolite.
g2 had a nasty rc file where every variable had to be declared. As a result,
ADCs that needed their own settings could not use it.
Now it's a perl hash, and you can add any keys you want.
Please look at the rc file that gets installed when you setup gitolite. As
you can see there are 3 types of variables in it:
* simple variables (like UMASK)
* lists (like `POST_COMPILE`, `POST_CREATE`)
* hashes (like `ROLES`, `COMMANDS`)
Their purposes are to be found in each of their individual documentation files
around; start with [customising gitolite][cust].

View File

@ -1,30 +0,0 @@
## #refex matching a ref and a refex
A refex is a word I made up to mean "a regex that matches a ref". If you know
[regular expressions][regex] you're halfway there.
The only extra info you need is:
* for convenience, a refex not starting with `refs/` is assumed to start
with `refs/heads/`. This means normal branches can be referred to like
this:
RW master = alice
# becomes 'refs/heads/master' internally
while tags will need to be fully qualified
RW refs/tags/v[0-9] = bob
* a refex is implicitly anchored at the start, but not at the end. In
regular expression lingo, a `^` is assumed at the start (but no `$` at the
end is assumed). So a refex of `master` will allow all these:
refs/heads/master
refs/heads/master1
refs/heads/master2
refs/heads/master/full
If you want to restrict to just the one specific ref, use
RW master$ = alice

View File

@ -1,23 +0,0 @@
## #repo repo definitions
Example:
repo gitolite tsh gitpod
RW+ = sitaram
RW dev = alice bob
R = @all
The "repo" line can have any number of repo names or repo group names in it.
However, it can only be one line; this will not work
repo foo
repo bar # WRONG; 'foo' is now forgotten
RW = alice
If you have too many, use a group name:
@myrepos = foo
@myrepos = bar
repo @myrepos
RW = alice

View File

@ -1,82 +0,0 @@
## #rules access rules
This is arguably the most complex part of day-to-day gitolite. There are
several interconnected ideas that make this hard to lay out easily if you're
totally new to this, so read carefully.
We will use this as a running example:
@staff = dilbert alice wally bob
repo foo
RW+ = dilbert # line 1
RW+ dev = alice # line 2
- = wally # line 3
RW temp/ = @staff # line 4
R = ashok # line 5
### when does gitolite check access
The "pre-git" check is before git is invoked. Gitolite knows the repo name,
user name, and attempted access (R or W), but no ref name.
The "update" check is only for write operations, and it is just before git
updates a ref. This time gitolite knows the refname also.
### how is a particular rule line matched
For the **pre-git check**, any permission that contains "R" matches a read
operation, and any permission that contains "W" matches a write operation.
This is because we simply don't know enough to make finer distinctions at this
point.
In addition, *gitolite ignores deny rules during the pre-git check*. <font
color="gray">(You can [change this][deny-rules] if you wish, though it's
rarely needed)</font>. This means line 3 is ignored, and so Wally in our
example will pass the pre-git check.
For the **update check**, git gives us all the information we need. Then:
* all the rules for a repo are [accumulated][rule-accum]
* then the rules pertaining to this repo *and* this user (or to a group to
which they belong, respectively) are kept; the rest are ignored
* these rules are examined *in the sequence they appeared in the conf file*.
For each rule:
* if the ref does not match the [refex][], the rule is skipped
* if it's a deny rule (the permissions field is a `-`), access is
**rejected** and the matching stops
* if the permission field matches the specific [type of
write][write-types] operation, access is **allowed** and the matching
stops
* if no rule ends with a decision, ("fallthru"), access is **rejected**.
Now you need to understand how [refex][] matching happens and how the
permissions match the various [types of write operations][write-types].
Using these, you can see, in our example, that:
* everyone, even wally, can read the repo.
* dilbert can push, rewind, or delete any ref.
* alice can push, rewind, or delete any ref whose name starts with 'dev';
see [refex][] for details.
* alice can also push (but not rewind or delete) any ref whose name starts
with 'temp/'. This applies to bob also.
* if it weren't for line 3, the previous statement would apply to wally
also.
Interestingly, wally can get past the pre-git check because gitolite ignores
deny rules for pre-git, but having got past it, he can't actually do anything.
That's by design, and as I said if you don't like it you can ask gitolite to
[deny at pre-git][deny-rules].
### summary of permissions
The full set of permissions, in regex syntax: `-|R|RW+?C?D?M?`. This expands
to one of `-`, `R`, `RW`, `RW+`, `RWC`, `RW+C`, `RWD`, `RW+D`, `RWCD`, or
`RW+CD`, all but the first one optionally followed by an `M`. And by now you
know what they all mean.

View File

@ -1,27 +0,0 @@
# testing gitolite
Here's how to *run* the tests. **WARNING: they will clobber lots of things in
your `$HOME`, so be sure to use a throwaway userid**.
git clone git://github.com/sitaramc/gitolite
cd gitolite
git checkout -f g3
prove
Gitolite's test suite is mostly written using [tsh][] -- the "testing shell".
Take a look at some of the scripts and you will see what it looks like. It
has a few quirks and nuances; if you really care, email me.
[tsh]: http://github.com/sitaramc/tsh
The tests also use a somewhat convoluted system of environment variables in
order to run *entirely* as a local user, without going through ssh at all.
This lets a complete test suite run in about a fifth or less of the time it
would otherwise take.
If you think that defeats the purpose of the testing, you haven't read
[this][auth] yet.
There are 2 specific tests that deal with ssh though, which are run only on
request, as you can see above, because they clobber your `~/.ssh`. You have
been warned.

View File

@ -1,108 +0,0 @@
# gitolite triggers
## intro and sample rc excerpt
Gitolite fires off external commands at 7 different times. The [rc][] file
specifies what commands to run at each trigger point, but for illustration,
here's an excerpt:
%RC = (
<...several lines later...>
# 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',
],
# comment out or uncomment as needed
# these will run in sequence after a new wild repo is created
POST_CREATE =>
[
'post-compile/update-git-configs',
'post-compile/update-gitweb-access-list',
'post-compile/update-git-daemon-access-list',
],
(As you can see, post-create runs 3 programs that also run from post-compile.
This is perfectly fine, by the way)
## manually firing triggers
...from the server command line is easy. For example:
gitolite trigger POST_COMPILE
However if the triggered code depends on arguments (see next section) this
won't work. (The `POST_COMPILE` trigger programs all just happen to not
require any arguments, so it works).
## common arguments
Triggers receive the following arguments:
1. any arguments mentioned in the rc file (for an example, see the renice
command in the PRE_GIT trigger sequence),
2. the name of the trigger as a string (example, `"POST_COMPILE"`), so you
can call the same program from multiple triggers and know where it was
called from,
3. followed by zero or more arguments specific to the trigger, as given in
the next section.
## trigger-specific details
Here's a brief "when" and "with what arguments" for each trigger.
* `ACCESS_1` runs after the first access check. Arguments:
* repo
* user
* 'R' or 'W'
* 'any'
* result: this is the result of the access() function. If it contains
the uppercase word "DENIED", the access was rejected. Otherwise
result contains the refex that caused the access to succeed.
* `ACCESS_2` runs after the second access check, in the update hook.
Arguments:
* repo
* user
* any of W, +, C, D, WM, +M, CM, DM
* the ref being updated (e.g., 'refs/heads/master')
* result (see above)
* `PRE_GIT` runs just before running the git command. Arguments:
* repo
* user
* 'R' or 'W'
* 'any'
* the git command ('git-receive-pack', 'git-upload-pack', or
'git-upload-archive') being invoked.
* `POST_GIT` runs after the git command returns. Arguments:
* repo
* user
* 'R' or 'W'
* 'any'
* the git command ('git-receive-pack', 'git-upload-pack', or
These are followed by the output of the perl function "times" (i.e., 4 CPU
times: user, system, cumulative user, cumulative system) so that's 9
arguments in total
* `PRE_CREATE` and `POST_CREATE` run just before and after a new "[wild][]"
repo is created by user action. Arguments:
* repo
* user
* `POST_COMPILE` runs after an admin push has successfully "compiled" the
config file. By default, the next thing is to update the ssh authkeys
file, then all the 'git-config's, gitweb access, and daemon access.
No arguments.

View File

@ -1,96 +0,0 @@
# what users (not admins) need to know about gitolite
...written for the one guy in the world no one will think of as "just a normal
user" ;-)
This document has some text, and a lot of links. Most of this info *is*
available in the rest of the documentation, but it's scattered and sparse.
Collecting all of it, or at least links to it, in one place sounds useful.
## accessing gitolite
The most common setup is based on ssh, where your admin asks you to send him
your public key, and uses that to setup your access.
Your actual access is either a git command (like `git clone
git@server:reponame`, and we won't be discussing these any more in this
document), or an ssh command (like `ssh git@server info`).
Note that you do *not* get a shell on the server -- the whole point of
gitolite is to prevent that!
## #info the info command
The only command that is *always* available to every user is the `info`
command (run `ssh git@host info -h` for help), which tells you what version of
gitolite and git are on the server, and what repositories you have access to.
The list of repos is very useful if you have doubts about the spelling of some
new repo that you know was setup.
## digression: two kinds of repos
Gitolite has two kinds of repos. Normal repos are specified by their full
names in the config file. "Wildcard" repos are specified by a regex in the
config file. Try the [`info` command][info] and see if it shows any lines
that look like regex patterns, (with a "C" permission in addition to the "R"
and the "W").
If you see any, it means you are allowed to create brand new repos whose names
fit that pattern. When you create such a repo, your "ownership" of it (as far
as gitolite is concerned) is *automatically* recorded by gitolite.
## other commands
### #perms set/get additional permissions for repos you created
The gitolite config may have several permissions lines for your repo, like so:
repo pub/CREATOR/..*
RW+ = CREATOR
RW = user1 user2
R = user3
If that's all it had, you really can't do much. Any changes to access must be
done by the administrator. (Note that "CREATOR" is a reserved word that gets
expanded to your userid in some way, so the admin can literally add just the
first two lines, and *every* authenticated user now has his own personal repo
namespace, starting with `pub/<username>/`).
To give some flexibility to users, the admin could add rules like this:
RW = WRITERS
R = READERS
(he could also add other roles but then he needs to read the documentation).
Once he does this, you can then use the `perms` command (run `ssh git@host
perms -h` for help) to set permissions for other users by specifying which
users are in the list of "READERS", and which in "WRITERS".
If you think of READERS and WRITERS as "roles", it will help. You can't
change what access a role has, but you *can* say which users have that role.
**Note**: there isn't a way for you to see the actual rule set unless you're
given read access to the special 'gitolite-admin' repo. Sorry. The idea is
that your admin will tell you what "roles" he added into rules for your repos,
and what permissions those roles have.
### #desc adding a description to repos you created
The `desc` command is extremely simple. Run `ssh git@host desc -h` for help.
## "site-local" commands
The main purpose of gitolite is to prevent you from getting a shell. But
there are commands that you often need to run on the server (i.e., cannot be
done by pushing something to a repo).
To enable this, gitolite allows the admin to setup scripts in a special
directory that users can then run. Gitolite comes with a set of working
scripts that your admin may install, or may use as a starting point for his
own, if he chooses.
Think of these commands as equivalent to those in `COMMAND_DIR` in `man
git-shell`.
You can get a list of available commands by running `ssh git@host help`.

View File

@ -1,254 +0,0 @@
# virtual refs
Here's an example to start you off.
repo r1
RW+ = lead_dev dev2 dev3
- VREF/COUNT/9 = dev2 dev3
- VREF/COUNT/3/NEWFILES = dev2 dev3
Now dev2 and dev3 cannot push changes that affect more than 9 files at a time,
nor those that have more than 3 new files.
Another example is detecting duplicate pubkeys in a push to the admin repo:
repo gitolite-admin
# ... normal rules ...
- VREF/DUPKEYS = @all
----
## rule matching recap
You won't get any joy out of this if you don't understand at least
[refex][]es and how [rules][] are processed.
But VREFs have one **very important difference** from normal rules. With
VREFs, a **fallthru results in success**. You'll see why this is more
convenient as you read on.
----
## what is a virtual ref
A ref like `refs/heads/master` is the main property of a push that gitolite
uses to make its yes/no decision. I call this a "real" ref.
Any *other* property of the push that you want to use to help in the decision
is therefore a *virtual* ref. This could be a property that git knows about,
like in the example above, or comes from outside git like, say, the current
time; see examples section later for some ideas.
## fallthru is success here
Notice that you didn't need to add an `RW+ VREF/...` rule for user `lead_dev`
in our example. This section explains why.
**Virtual refs are best used as additional "deny" rules**, performing extra
checks that core gitolite cannot.
Making fallthru be a "fail" forces you to add rules for all users, instead of
just the ones who should have those extra checks. Worse, since every virtual
ref involves calling an external program, many of these calls may be wasted.
There's another advantage to doing it this way: a VREF can choose to simply
die if things look bad, and it will have the same effect, assuming you used
the VREF only in [deny][] rules.
This in turn means any existing update hook can be used as a VREF *as-is*, as
long as it (a) prints nothing on success and (b) dies on failure. See the
email-check and dupkeys examples later.
## how it works -- overview
Briefly, a refex starting with `VREF/FOO` triggers a call to a program called
`FOO` in `$GL_BINDIR/VREF`.
That program is expected to print zero or more lines to its STDOUT; each line
is taken by gitolite as a new "ref" to be matched against all the refexes for
this user in the config. Including the refex that caused the vref call, of
course.
Normally, you send back the refex itself, if the test determines that the rule
should be matched, otherwise nothing. So, in our example, we print
`VREF/COUNT/9` if the count was indeed greater than 9. Otherwise we just
exit.
## how it works -- details
* the VREF code is only called if there are any VREF rules for the user,
which means when the lead developer pushes, the VREF is not called at all.
Side note: this is enormously more efficient than adding additional
`update` hooks, which will get executed whether they are needed or not,
for every repo and every user!
* when dev2 or dev3 push, gitolite first checks the real ref
(`ref/heads/master` or whatever). After this it looks at VREF rules, and
calls an external program for every one it finds. Specifically, in a line
like
- VREF/COUNT/3/NEWFILES = user
COUNT is the vref name, so the program called is
`$GL_BINDIR/VREF/COUNT`.
The program is passed **nine arguments** in this case (see next section
for details).
* the script can print anything it wants to STDOUT; the first word in each
such line will be treated as a virtual ref to be matched against all the
rules, while the rest, if any, is a message to be added to the standard
"...DENIED..." message that gitolite prints if that refex matches.
Usually it only makes sense to either
* print nothing -- if you don't want the rule that triggered it to match
(ie., whatever condition being tested was not violated; like if the
count of changed files did not exceed 9, in our earlier example)
* print the refex itself (plus an optional message), so that it matches
the line which invoked it
### arguments passed to the vref code
* arguments **1, 2, 3**: the 'ref', 'oldsha', and 'newsha' that git passed
to the update hook (see 'man githooks')
This, combined with the fact that non-zero exits are detected, mean that
you can simply use an existing update.secondary as a new VREF as-is, no
changes needed.
* arguments **4 and 5**: the 'oldtree' and 'newtree' SHAs. These are the
same as the oldsha and newsha values, except if one of them is all-0.
(indicating a ref creation or deletion). In that case the corresponding
'tree' SHA is set (by gitolite, as a courtesy) to the special SHA
`4b825dc642cb6eb9a060e54bf8d69288fbee4904`, which is the hash of an empty
tree.
(None of these shenanigans would have been needed if `git diff $oldsha
$newsha` would not error out when passed an all-0 SHA.)
* argument **6**: the attempted access flag. Typically `W` or `+`, but
could also be `C`, `D`, or any of these 4 followed by `M`. If you have to
ask what they mean, you haven't read enough gitolite documentation to be
able to make virtual refs work.
* argument **7**: is the entire refex; in our example
`VREF/COUNT/3/NEWFILES`.
* arguments **8 onward**: are the split out (by `/`) portions of the refex,
excluding the first two components. In our example they would be `3`
followed by `NEWFILES`.
Yes, argument 7 is redundant if you have 8 and 9. It's meant to make it easy
to write vref scripts in any language. See script examples in source.
## what (else) can the vref code pass back
Actually, the vref code can pass anything back; each line in its output will
be matched against all the rules as usual (with the exception that fallthru is
not failure).
For example, you could have a ruleset like this:
repo r1
# ... normal rules ...
- VREF/TIME/WEEKEND = @interns
- VREF/TIME/WEEKNIGHT = @interns
- VREF/TIME/HOLIDAY = @interns
and you could write the TIME vref code to passback any or all
of the times that match. Then if an intern tried to access the system, each
rule would trigger a call to gl-bindir/VREF/TIME.
The script should send back any of the applicable times (even more than one,
or none at all, as the case may be). So even if it was invoked using the
first rule, it might pass back (to gitolite) a virtual ref saying
'VREF/TIME/HOLIDAY', which would promptly cause the request to be denied.
## VREFs shipped with gitolite
### number of new files
If a dev pushes more than 2 *new* files, the top commit needs to have a
signed-off by line in its commit message. For example if he has 4 new files
this text should be:
4 new files signed-off by: <top commit author's email>
The config entry for this is below (`NO_SIGNOFF` applies only to, and thus
implies, `NEWFILES`):
RW+ VREF/COUNT/2/NO_SIGNOFF = sitaram
- VREF/COUNT/2/NO_SIGNOFF = @all
Notice how the refex in both cases is *exactly* the same. If you make it
different (even change the number on my access line), things won't work.
Junior devs can't push more than 10 new files, even with a signed-off by line:
- VREF/COUNT/10/NEWFILES = @junior_devs
### advanced filetype detection
Note: this is more for illustration than use; it's rather specific to one of
the projects I manage but the idea is the important thing.
Sometimes a file has a standard extension (that cannot be 'gitignore'd), but
it is actually automatically generated. Here's one way to catch it:
- VREF/FILETYPE/AUTOGENERATED = @all
You can look at `src/VREF/FILETYPE` to see how it handles the
'AUTOGENERATED' option. You could also have a more generic option, like
perhaps BINARY, and handle that in the FILETYPE vref too.
### checking author email
Some people want to ensure that "you can only push your own commits".
If you force it on everyone, this is a very silly idea (see "Philosophical
Notes" section of `src/VREF/EMAIL-CHECK`).
But there may be value in enforcing it just for the junior developers.
The neat thing is that the existing `contrib/update.email-check` was just
copied to `src/VREF/EMAIL-CHECK` and it works, because VREFs get
the same first 3 arguments and those are all that it cares about. (Note: you
have to change one subroutine in that script if you want to use it)
### catching duplicate pubkeys
We covered this as a teaser example at the start.
## other ideas -- code welcome!
### "no non-merge first-parents"
Shruggar on #gitolite wanted this. Possible code to implement it would be
something like this (untested)
[ -z "$(git rev-list --first-parent --no-merges $2..$3)" ]
This can be implemented using `src/VREF/MERGE-CHECK` as a model.
That script does what the 'in core' feature called [merge check][mergecheck]
does, although the syntax to be used in conf/gitolite will be quite different.
### other ideas for VREFs
Here are some more ideas:
* number of commits (`git rev-list --count $old $new`)
* number of binary files in commit (currently I only know to count
occurrences of ` Bin ` in the output of `git diff --stat`
* number of *new* binary files (count ` Bin 0 ->` in `git diff --stat`
output)
* time of day/day of week (see example snippet somewhere above)
* IP address
* phase of the moon
Note that pretty much anything that involves `$oldsha..$newsha` will have to
deal with the issue that when you push a new tag or branch, the "old" part
is all 0's, and unless you consider `--all` existing branches and tags it
becomes meaningless in terms of "number of new files" etc.

View File

@ -1,46 +0,0 @@
# Why is gitolite needed?
Gitolite is separate from git, and needs to be installed and configured. So...
why do we bother?
Gitolite is useful in any server that is going to host multiple git
repositories, each with many developers, where some sort of access control is
required.
In theory, this can be done with plain old Unix permissions: each user is a
member of one or more groups, each group "owns" one or more repositories, and
using unix permissions (especially the setgid bit -- `chmod g+s`) you can
allow/disallow users access to repos.
But there are several disadvantages here:
* every user needs a userid and password on the server. This is usually a
killer, especially in tightly controlled environments
* adding/removing access rights involves complex `usermod -G ...` mumblings
which most admins would rather not deal with
* *viewing* (aka auditing) the current set of permissions requires running
multiple commands to list directories and their permissions/ownerships,
users and their group memberships, and then correlating all these manually
* auditing historical permissions or permission changes is pretty much
impossible without extraneous tools
* errors or omissions in setting the permissions exactly can cause problems
of either kind: false accepts or false rejects
* without going into ACLs it is not possible to give some people read-only
access while some others have read-write access to a repo (unless you make
it world-readable). Group access just doesn't have enough granularity
* it is absolutely impossible to restrict pushing by branch name or tag
name.
Gitolite does away with all this:
* it uses ssh magic to remove the need to give actual unix userids to
developers
* it uses a simple but powerful config file format to specify access rights
* access control changes are affected by modifying this file, adding or
removing user's public keys, and "compiling" the configuration
* this also makes auditing trivial -- all the data is in one place, and
changes to the configuration are also logged, so you can audit them.
* finally, the config file allows distinguishing between read-only and
read-write access, not only at the repository level, but at the branch
level within repositories.

View File

@ -1,31 +0,0 @@
## #write-types different types of write operations
Git supplies enough information to the update hook to be able to distinguish
several types of writes.
The most common are:
* `RW` -- create a ref or fast-forward push a ref. No rewinds or deletes.
* `RW+` -- create, fast-forward push, rewind push, or delete a ref.
Sometimes you want to allow people to push, but not *create* a ref. Or
rewind, but not *delete* a ref. The `C` and `D` qualifiers help here.
* when a rule specifies `RWC` or `RW+C`, then *rules that do NOT have the C
qualifier will no longer permit **creating** a ref*
<font color="gray">Please do not confuse this with the standalone `C`
permission that allows someone to [create][] a **repo**</font>
* when a rule specifies `RWD` or `RW+D`, then *rules that do NOT have the C
qualifier will no longer permit **deleting** a ref*
Note: These two can be combined, so you can have `RWCD` and `RW+CD` as well.
One very rare need is to reject merge commits (a commit series that is not a
straight line of commits). The `M` qualifier helps here:
* When a rule has `M` appended to the permissions, *rules that do NOT have
it will reject a commit sequence that contains a merge commit* (i.e., they
only accept a straight line series of commits).

31
dot.pl
View File

@ -1,31 +0,0 @@
#!/usr/bin/perl
use strict;
use warnings;
my @a = `grep -r use.Gitolite . | grep -i '^./gitolite'`;
# chomp(@a);
open( my $fh, "|-", "tee module-tree.gv | dot -Tpng | tee module-tree.png | display" );
@a = map {
print $fh "#$_";
s/^\.\/gitolite\///i;
s/-/_/g;
s/\.\///;
s/\//_/g;
s/\.pm:/ -> /;
s/use Gitolite:://;
s/::/_/g;
s/:/ -> /;
s/;//;
s/^(\S+) -> \1$//;
s/.* -> Rc//;
s/.* -> Common//;
$_;
} @a;
# open(my $fh, "|-", "cat > /tmp/junkg3");
print $fh "digraph G {\n";
print $fh $_ for @a;
print $fh "}\n";
close $fh;

77
install Executable file
View 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 );
}

View File

@ -1,5 +1,4 @@
#!/bin/bash
# TODO: convert to perl!
#!/bin/sh
# gitolite VREF to count number of changed/new files in a push
@ -32,9 +31,9 @@ nf=
# 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 | wc -l`
count=`git log --name-only $nf --format=%n $newtree --not --all | grep . | sort -u | perl -ne '}{print "$."'`
[[ $count -gt $max ]] && {
[ $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" ] && {

View File

@ -1,45 +0,0 @@
#!/bin/bash
# gitolite VREF to detect duplicate public keys
# see gitolite doc/vref.mkd for what the arguments are
sha=$3
# git sets this; and we don't want it at this point...
unset GIT_DIR
# paranoia
set -e
# setup the temp area
export TMPDIR=$GL_REPO_BASE_ABS
export tmp=$(mktemp -d -t gl-internal-temp-repo.XXXXXXXXXX);
trap "rm -rf $tmp" EXIT;
git archive $sha keydir | tar -C $tmp -xf -
# DO NOT try, say, 'GIT_WORK_TREE=$tmp git checkout $sha'. It'll screw up
# both the 'index' and 'HEAD' of the repo.git. Screwing up the index is
# BAD because now it goes out of sync with $GL_ADMINDIR. Think of a push
# that had a deleted pubkey but failed a hooklet for some reason. A
# subsequent push that fixes the error will now result in a $GL_ADMINDIR
# that still *has* that deleted pubkey!!
# And this is equally applicable to cases where you're using a
# post-receive or similar hook to live update a web site or something,
# which is a pretty common usage, I am given to understand.
cd $tmp
for f in `find keydir -name "*.pub"`
do
ssh-keygen -l -f "$f"
done | perl -ane '
die "FATAL: $F[2] is a duplicate of $seen{$F[1]}\n" if $seen{$F[1]};
$seen{$F[1]} = $F[2];
'
# as you can see, a vref can also 'die' if it wishes to, and it'll take the
# whole update with it if it does. No messing around with sending back a
# vref, having it run through the matches, and printing the DENIED message,
# etc. However, if your push is running from a script, and that script is
# looking for the word "DENIED" or something, then this won't work...

View File

@ -1,5 +1,4 @@
#!/bin/bash
# TODO: convert to perl!
#!/bin/sh
# gitolite VREF to find autogenerated files

40
src/VREF/MAX_NEWBIN_SIZE Executable file
View 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

80
src/VREF/VOTES Executable file
View 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
View 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

View File

@ -20,13 +20,22 @@ exec >&2
main=`git config --file $GL_REPO_BASE/$repo.git/config --get gitolite.partialCopyOf`;
[ -z "$main" ] && exit 0
rand=$RANDOM
rand=$$
export GL_BYPASS_ACCESS_CHECKS=1
git push -f $GL_REPO_BASE/$main.git $new:refs/heads/br-$rand || die "FATAL: failed to send $new"
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/heads/br-$rand
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
View 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
View 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

View File

@ -2,7 +2,7 @@
use strict;
use warnings;
use lib $ENV{GL_BINDIR};
use lib $ENV{GL_LIBDIR};
use Gitolite::Rc;
use Gitolite::Common;
use Gitolite::Conf::Load;
@ -42,15 +42,13 @@ 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)$/ );
_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
_die "invalid repo name" if not( $repo and $repo =~ $REPONAME_PATT );
_die "invalid user name" if not( $user and $user =~ $USERNAME_PATT );
$ret = access( $repo, $user, $aa, $ref );
if ( $ret =~ /DENIED/ ) {

15
src/commands/create Executable file
View 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

View File

@ -2,7 +2,7 @@
use strict;
use warnings;
use lib $ENV{GL_BINDIR};
use lib $ENV{GL_LIBDIR};
use Gitolite::Rc;
use Gitolite::Common;
use Gitolite::Conf::Load;

59
src/commands/fork Executable file
View 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

View File

@ -3,7 +3,7 @@ use strict;
use warnings;
use Getopt::Long;
use lib $ENV{GL_BINDIR};
use lib $ENV{GL_LIBDIR};
use Gitolite::Rc;
use Gitolite::Common;
use Gitolite::Conf::Load;
@ -23,8 +23,7 @@ Examples:
gitolite git-config -q repo gitweb.owner
gitolite git-config -r repo gitweb
When the key is treated as a pattern, prints one key+value per line, tab
separated:
When the key is treated as a pattern, prints:
reponame<tab>key<tab>value<newline>
@ -57,10 +56,17 @@ if ( $repo ne '%' and $key ne '%' ) {
$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;
map { print "$repo\t$_\t" . $ret->{$_} . "\n" } sort keys %$ret unless $quiet;
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;
}

View File

@ -2,7 +2,7 @@
use strict;
use warnings;
use lib $ENV{GL_BINDIR};
use lib $ENV{GL_LIBDIR};
use Gitolite::Rc;
use Gitolite::Common;
@ -20,20 +20,23 @@ usage() if @ARGV;
my $user = $ENV{GL_USER} || '';
print "hello" . ( $user ? " $user" : "" ) . ", this is gitolite3 " . version() . " on git " . substr( `git --version`, 12 ) . "\n";
_chdir("$ENV{GL_BINDIR}/commands");
print "list of " . ( $user ? "remote" : "gitolite" ) . " commands available:\n\n";
for my $c (`find . -type f|sort`) {
chomp($c);
$c =~ s(^./)();
next unless -x $c;
# if it's from a remote client, show only what he is allowed
next if $user and not $rc{COMMANDS}{$c};
print "\t$c\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
View 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;

View File

@ -4,17 +4,19 @@ use warnings;
use Getopt::Long;
use lib $ENV{GL_BINDIR};
use lib $ENV{GL_LIBDIR};
use Gitolite::Rc;
use Gitolite::Common;
use Gitolite::Conf::Load;
=for args
Usage: gitolite info [-lc] [<repo name pattern>]
Usage: gitolite info [-lc] [-ld] [<repo name pattern>]
List all repos/repo groups you can access.
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
@ -22,7 +24,7 @@ than a few thousand repos.
=cut
# these two are globals
my ( $lc, $patt ) = args();
my ( $lc, $ld, $patt ) = args();
print_version();
@ -33,25 +35,26 @@ print "\n$rc{SITE_INFO}\n" if $rc{SITE_INFO};
# ----------------------------------------------------------------------
sub args {
my ( $lc, $patt ) = ( '', '' );
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, $patt );
return ( $lc, $ld, $patt );
}
sub print_version {
chomp( my $hn = `hostname -s` );
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}\@$hn running gitolite3 " . version() . " on git $gv\n";
print "hello $ENV{GL_USER}, this is " . ($ENV{USER} || "httpd") . "\@$hn running gitolite3 " . version() . " on git $gv\n";
}
sub print_patterns {
@ -60,8 +63,8 @@ sub print_patterns {
# 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' flag for these
listem( $repos, '', '', @aa );
# but squelch the 'lc' and 'ld' flags for these
}
sub print_phy_repos {
@ -71,17 +74,24 @@ sub print_phy_repos {
_chdir( $rc{GL_REPO_BASE} );
$repos = list_phy_repos(1);
@aa = qw(R W);
listem( $repos, $lc, @aa );
listem( $repos, $lc, $ld, @aa );
}
sub listem {
my ( $repos, $lc, @aa ) = @_;
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" );
@ -90,6 +100,7 @@ sub listem {
next unless $perm =~ /\S/;
print "$perm\t$repo";
print "\t$creator" if $lc;
print "\t$desc" if $ld;
print "\n";
}
}

View 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
View 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
View 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};
}

View File

@ -2,7 +2,7 @@
use strict;
use warnings;
use lib $ENV{GL_BINDIR};
use lib $ENV{GL_LIBDIR};
use Gitolite::Rc;
use Gitolite::Common;
use Gitolite::Conf::Load;
@ -21,12 +21,21 @@ Examples:
ssh git@host perms foo + READERS user2
ssh git@host perms foo + READERS user3
(Note: a legacy mode of piping in the entire permissions text directly is also
supported. If you want to use it, don't mix it with the new "+/-" modes).
----
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++;
@ -34,14 +43,34 @@ if ( $ARGV[0] eq '-l' ) {
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" );
_system( "gitolite", "trigger", "POST_CREATE", $repo, $ENV{GL_USER}, 'perms' );
# ----------------------------------------------------------------------
sub getperms {
my $repo = shift;
_die "sorry you are not authorised" if repo_missing($repo) or creator($repo) ne $ENV{GL_USER};
_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;
@ -50,18 +79,20 @@ sub getperms {
}
sub setperms {
my $repo = shift;
_die "sorry you are not authorised" if repo_missing($repo) or creator($repo) ne $ENV{GL_USER};
_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;
}

View File

@ -2,7 +2,7 @@
use strict;
use warnings;
use lib $ENV{GL_BINDIR};
use lib $ENV{GL_LIBDIR};
use Gitolite::Rc;
print glrc('default-text');

5
src/commands/push Executable file
View File

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

149
src/commands/rsync Executable file
View 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
View 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
View 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
View 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
View 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
View 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
View 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'");
}
}

View File

@ -2,12 +2,15 @@
use strict;
use warnings;
use lib $ENV{GL_BINDIR};
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
@ -22,9 +25,9 @@ my $repo = shift;
my $on = ( shift eq 'on' );
if ( $repo eq '@all' ) {
die "you are not authorized\n" if $ENV{GL_USER} and not is_admin();
_die "you are not authorized" if $ENV{GL_USER} and not is_admin();
} else {
die "you are not authorized\n" if $ENV{GL_USER} and not owns($repo);
_die "you are not authorized" if $ENV{GL_USER} and not owns($repo);
}
my $msg = join( " ", @ARGV );

View File

@ -36,7 +36,8 @@ written.
use FindBin;
BEGIN { $ENV{GL_BINDIR} = $FindBin::RealBin; }
use lib $ENV{GL_BINDIR};
BEGIN { $ENV{GL_LIBDIR} = "$ENV{GL_BINDIR}/lib"; }
use lib $ENV{GL_LIBDIR};
use Gitolite::Rc;
use Gitolite::Common;
@ -72,9 +73,10 @@ if ( $command eq 'setup' ) {
} elsif ( $command eq 'trigger' ) {
trigger(@args);
} elsif ( -x "$rc{GL_BINDIR}/commands/$command" ) {
trace( 2, "attempting gitolite command $command" );
run_command( $command, @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} );
@ -98,11 +100,3 @@ sub args {
}
# ----------------------------------------------------------------------
sub run_command {
my $pgm = shift;
my $fullpath = "$ENV{GL_BINDIR}/commands/$pgm";
_die "$pgm not found or not executable" if not -x $fullpath;
_system( $fullpath, @_ );
exit 0;
}

View File

@ -6,7 +6,12 @@
use FindBin;
BEGIN { $ENV{GL_BINDIR} = $FindBin::RealBin; }
use lib $ENV{GL_BINDIR};
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;
@ -26,6 +31,12 @@ if ( exists $ENV{G3T_USER} ) {
_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);
@ -47,19 +58,29 @@ sub in_file {
}
sub in_http {
_die 'http not yet implemented...';
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" );
gl_log( 'ssh', "ARGV=" . join( ",", @ARGV ), "SOC=" . ( $ENV{SSH_ORIGINAL_COMMAND} || '' ), "FROM=$ip" );
$ENV{SSH_ORIGINAL_COMMAND} ||= '';
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;
return $ip;
}
@ -86,8 +107,8 @@ sub main {
if ( repo_missing($repo) and access( $repo, $user, '^C', 'any' ) !~ /DENIED/ ) {
require Gitolite::Conf::Store;
Gitolite::Conf::Store->import;
new_wild_repo( $repo, $user );
gl_log( 'create', $repo, $user );
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
@ -104,9 +125,13 @@ sub main {
}
trigger( 'PRE_GIT', $repo, $user, $aa, 'any', $verb );
my $repodir = "'$rc{GL_REPO_BASE}/$repo.git'";
_system( "git", "shell", "-c", "$verb $repodir" );
trigger( 'POST_GIT', $repo, $user, $aa, 'any', $verb, times() );
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 );
}
# ----------------------------------------------------------------------
@ -136,12 +161,79 @@ sub parse_soc {
exit 0;
}
_die "unknown git/gitolite command: $soc";
_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(\.\.$);
_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";
}

View File

@ -12,6 +12,10 @@ package Gitolite::Common;
usage tsh_run
gen_lfn
gl_log
dd
t_start
t_lap
);
#>>>
use Exporter 'import';
@ -63,7 +67,28 @@ sub dbg {
}
}
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} ) ) {
@ -72,6 +97,7 @@ sub _warn {
warn "WARNING: ", @_, "\n";
}
}
$SIG{__WARN__} = \&_warn;
sub _die {
gl_log( 'die', @_ );
@ -83,6 +109,7 @@ sub _die {
die "FATAL: " . join( ",", @_ ) . "\n";
}
}
$SIG{__DIE__} = \&_die;
sub usage {
_warn(shift) if @_;
@ -248,17 +275,18 @@ sub gl_log {
my $tid = $ENV{GL_TID} ||= $$;
my $fh;
logger_plus_stderr( "$ts no GL_LOGFILE env var", "$ts $msg" ) if not $ENV{GL_LOGFILE};
open my $lfh, ">>", $ENV{GL_LOGFILE} or logger_plus_stderr( "open log failed: $!", $msg );
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 ( "FATAL: have errors but logging failed!\n", @_ ) {
print STDERR "$_\n";
print $fh "$_\n";
for ( @_ ) {
print STDERR "FATAL: $_\n";
print $fh "FATAL: $_\n";
}
exit 1;
}
@ -274,7 +302,7 @@ sub logger_plus_stderr {
sub tsh_try {
my $cmd = shift; die "try: expects only one argument" if @_;
$text = `( $cmd ) 2>&1; echo -n RC=\$?`;
$text = `( $cmd ) 2>&1; printf RC=\$?`;
if ( $text =~ s/RC=(\d+)$// ) {
$rc = $1;
trace( 3, $text );

View File

@ -34,6 +34,10 @@ sub compile {
# place to put the individual gl-conf files
new_repos();
store();
for my $repo ( @{ $rc{NEW_REPOS_CREATED} } ) {
trigger( 'POST_CREATE', $repo );
}
}
sub parse {
@ -58,10 +62,11 @@ sub parse {
}
} 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 "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+)$/ ) {

View File

@ -27,7 +27,7 @@ sub explode {
my ( $file, $subconf, $out ) = @_;
# seed the 'seen' list if it's empty
$included{ device_inode("conf/gitolite.conf") }++ unless %included;
$included{ device_inode("gitolite.conf") }++ unless %included;
my $fh = _open( "<", $file );
while (<$fh>) {
@ -52,7 +52,7 @@ 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 "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 =~ /^"(.+)"$/
@ -63,7 +63,7 @@ sub incsub {
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$);
_die "invalid include/subconf filename '$file'" unless $file =~ m(([^/]+).conf$);
my $basename = $1;
next if already_included($file);

View File

@ -32,6 +32,7 @@ our $data_version = '';
our %repos;
our %one_repo;
our %groups;
our %patterns;
our %configs;
our %one_config;
our %split_conf;
@ -67,11 +68,18 @@ my $last_repo = '';
sub access {
my ( $repo, $user, $aa, $ref ) = @_;
my $deny_rules = option( $repo, 'deny-rules' );
_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;
_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.
@ -80,8 +88,12 @@ sub access {
$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";
}
my @rules = rules( $repo, $user );
trace( 2, scalar(@rules) . " rules found" );
for my $r (@rules) {
my $perm = $r->[1];
@ -109,11 +121,14 @@ sub access {
}
sub git_config {
my ( $repo, $key ) = @_;
my ( $repo, $key, $empty_values_OK ) = @_;
$key ||= '.';
return {} if repo_missing($repo);
load($repo);
if (repo_missing($repo)) {
load_common();
} else {
load($repo);
}
# read comments bottom up
my %ret =
@ -144,6 +159,23 @@ sub git_config {
# 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;
}
@ -156,8 +188,18 @@ sub option {
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";
}
@ -177,11 +219,11 @@ sub load_common {
my $cc = "conf/gitolite.conf-compiled.pm";
_die "parse $cc failed: " . ( $! or $@ ) unless do $cc;
_die "parse '$cc' failed: " . ( $! or $@ ) unless do $cc;
if ( data_version_mismatch() ) {
_system("gitolite setup");
_die "parse $cc failed: " . ( $! or $@ ) unless do $cc;
_die "parse '$cc' failed: " . ( $! or $@ ) unless do $cc;
_die "data version update failed; this is serious" if data_version_mismatch();
}
}
@ -192,7 +234,7 @@ sub load_1 {
trace( 3, $repo );
if ( repo_missing($repo) ) {
trace( 1, "repo '$repo' missing" );
trace( 1, "repo '$repo' missing" ) if $repo =~ $REPONAME_PATT;
return;
}
_chdir("$rc{GL_REPO_BASE}/$repo.git");
@ -204,16 +246,16 @@ sub load_1 {
}
if ( -f "gl-conf" ) {
_warn "split conf not set, gl-conf present for $repo" if not $split_conf{$repo};
return if not $split_conf{$repo};
my $cc = "gl-conf";
_die "parse $cc failed: " . ( $! or $@ ) unless do $cc;
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};
_die "split conf set, gl-conf not present for '$repo'" if $split_conf{$repo};
}
}
@ -236,7 +278,7 @@ sub load_1 {
for my $r (@repos) {
for my $u (@users) {
push @rules, @{ $repos{$r}{$u} } if exists $repos{$r}{$u};
push @rules, @{ $repos{$r}{$u} } if exists $repos{$r} and exists $repos{$r}{$u};
}
}
@ -266,9 +308,11 @@ sub load_1 {
sub memberships {
trace( 3, @_ );
my ( $type, $base, $repo ) = @_;
$repo ||= '';
my @ret;
my $base2 = '';
my @ret = ( $base, '@all' );
@ret = ( $base, '@all' );
if ( $type eq 'repo' ) {
# first, if a repo, say, pub/sitaram/project, has a gl-creator file
@ -283,12 +327,16 @@ sub memberships {
}
}
for my $i ( keys %groups ) {
if ( $base eq $i or $base =~ /^$i$/ or $base2 and ( $base2 eq $i or $base2 =~ /^$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
@ -338,6 +386,8 @@ sub user_roles {
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} ) {
@ -365,12 +415,12 @@ sub generic_name {
# 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.
# 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(/$creator/)(/CREATOR/) if $creator;
$base2 =~ s(^$creator/)(CREATOR/) if $creator;
$base2 =~ s(\b$creator\b)(CREATOR) if $creator;
$base2 = '' if $base2 eq $base; # if there was no change
return $base2;
@ -378,6 +428,8 @@ sub generic_name {
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 = '';
@ -385,6 +437,20 @@ sub creator {
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
# ----------------------------------------------------------------------

View File

@ -63,12 +63,20 @@ sub add_to_group {
}
sub set_repolist {
@repolist = @_;
@repolist = ();
# ...sanity checks
for (@repolist) {
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, $_;
}
}
@ -103,13 +111,6 @@ sub add_rule {
$nextseq++;
for my $repo (@repolist) {
if ( check_subconf_repo_disallowed( $subconf, $repo ) ) {
my $repo = $repo;
$repo =~ s/^\@$subconf\./locally modified \@/;
$ignored{$subconf}{$repo} = 1;
next;
}
push @{ $repos{$repo}{$user} }, [ $nextseq, $perm, $ref ];
}
}
@ -137,7 +138,7 @@ sub expand_list {
for my $item (@list) {
if ( $item =~ /^@/ and $item ne '@all' ) # nested group
{
_die "undefined group $item" unless $groups{$item};
_die "undefined group '$item'" unless $groups{$item};
# add those names to the list
push @new_list, sort keys %{ $groups{$item} };
} else {
@ -155,13 +156,20 @@ sub new_repos {
# normal repos
my @repos = grep { $_ =~ $REPONAME_PATT and not /^@/ } sort keys %repos;
# add in members of repo groups
map { push @repos, keys %{ $groups{$_} } } grep { /^@/ } keys %repos;
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
new_repo($repo) if not -d "$repo.git";
# 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);
}
}
}
@ -177,14 +185,14 @@ sub new_repo {
}
sub new_wild_repo {
my ( $repo, $user ) = @_;
my ( $repo, $user, $aa ) = @_;
_chdir( $rc{GL_REPO_BASE} );
trigger( 'PRE_CREATE', $repo, $user );
trigger( 'PRE_CREATE', $repo, $user, $aa );
new_repo($repo);
_print( "$repo.git/gl-creator", $user );
_print( "$repo.git/gl-perms", "$rc{DEFAULT_ROLE_PERMS}\n" ) if $rc{DEFAULT_ROLE_PERMS};
trigger( 'POST_CREATE', $repo, $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} );
}
@ -250,15 +258,18 @@ sub store_1 {
# warning: writes and *deletes* it from %repos and %configs
my ($repo) = shift;
trace( 3, $repo );
return unless $repos{$repo} and -d "$repo.git";
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;
$one_repo{$repo} = $repos{$repo};
delete $repos{$repo};
my $dumped_data = Data::Dumper->Dump( [ \%one_repo ], [qw(*one_repo)] );
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};
@ -277,6 +288,8 @@ sub store_common {
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)] );
@ -290,7 +303,16 @@ sub store_common {
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";
@ -316,14 +338,13 @@ sub store_common {
$hook_reset++;
}
# propagate user hooks
# 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" );
# propagate admin hook
# 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';
# g2 diff: no "site-wide" hooks (the stuff in between gitolite hooks
# and user hooks) anymore. I don't think anyone used them anyway...
}
}

View File

@ -4,7 +4,7 @@ package SugarBox;
sub run_sugar_script {
my ( $ss, $lref ) = @_;
do $ss if -x $ss;
do $ss if -r $ss;
$lref = sugar_script($lref);
return $lref;
}
@ -52,9 +52,9 @@ sub 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 = "$ENV{GL_BINDIR}/syntactic-sugar/$s";
my $sfp = _which("syntactic-sugar/$s", 'r');
_warn("skipped sugar script '$s'"), next if not -x $sfp;
_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 ];
}
@ -119,29 +119,29 @@ sub owner_desc {
# owner = "owner name"
# -> config gitweb.owner = owner name
# description = "some long description"
# 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"
# repo "owner name" = "some long description"
# -> config gitweb.owner = owner name
# -> config gitweb.description = some long description
for my $line (@$lines) {
if ( $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;
} elsif ( $line =~ /^desc = (\S.*)/ ) {
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;
}

View File

@ -5,6 +5,25 @@ package Gitolite::Easy;
# 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
@ -25,6 +44,8 @@ package Gitolite::Easy;
%rc
say
say2
_die
_warn
_print
usage
);
@ -110,14 +131,18 @@ sub can_read {
}
# can_write()
# return true if $ENV{GL_USER} is set and can write to the given repo
# 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 = shift;
return not( access( $r, $user, 'W', 'any' ) =~ /DENIED/ );
my ($r, $aa, $ref) = @_;
$aa ||= 'W';
$ref ||= 'any';
return not( access( $r, $user, $aa, $ref ) =~ /DENIED/ );
}
# config()

View File

@ -23,14 +23,23 @@ sub post_update {
# 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)$/;
_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("$ENV{GL_BINDIR}/gitolite compile");
_system("$ENV{GL_BINDIR}/gitolite trigger POST_COMPILE");
_system("gitolite compile");
_system("gitolite setup --hooks-only") if $hooks_changed;
_system("gitolite trigger POST_COMPILE");
exit 0;
}
@ -55,10 +64,7 @@ __DATA__
use strict;
use warnings;
BEGIN {
die "GL_BINDIR not set; aborting\n" unless $ENV{GL_BINDIR};
}
use lib $ENV{GL_BINDIR};
use lib $ENV{GL_LIBDIR};
use Gitolite::Hooks::PostUpdate;
# gitolite post-update hook (only for the admin repo)

View File

@ -22,12 +22,14 @@ 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 );
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 );
@ -37,6 +39,13 @@ sub update {
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;
@ -52,15 +61,21 @@ sub check_vrefs {
}
} else {
my ( $dummy, $pgm, @args ) = split '/', $vref;
$pgm = "$ENV{GL_BINDIR}/VREF/$pgm";
-x $pgm or die "$vref: helper program missing or unexecutable\n";
$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: $!\n";
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 $!
close($fh) or _die $!
? "Error closing sort pipe: $!"
: "$vref: helper program exit status $?";
}
@ -73,6 +88,7 @@ sub check_vref {
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/;
@ -143,11 +159,7 @@ __DATA__
use strict;
use warnings;
BEGIN {
exit 0 if $ENV{GL_BYPASS_ACCESS_CHECKS};
die "GL_BINDIR not set; aborting\n" unless $ENV{GL_BINDIR};
}
use lib $ENV{GL_BINDIR};
use lib $ENV{GL_LIBDIR};
use Gitolite::Hooks::Update;
# gitolite update hook

View File

@ -9,6 +9,7 @@ package Gitolite::Rc;
query_rc
version
trigger
_which
$REMOTE_COMMAND_PATT
$REF_OR_FILENAME_PATT
@ -29,36 +30,43 @@ our %rc;
# ----------------------------------------------------------------------
# variables that could be overridden by the rc file
# pre-populate some important rc keys
# ----------------------------------------------------------------------
$rc{GL_BINDIR} = $ENV{GL_BINDIR};
$rc{GL_REPO_BASE} = "$ENV{HOME}/repositories";
$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._\@+-]*$);
#<<<
$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.0";
my $current_data_version = "3.2";
my $rc = glrc('filename');
do $rc if -r $rc;
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/g3/g2migr.html)";
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;
}
@ -71,17 +79,19 @@ if ( defined($GL_ADMINDIR) ) {
# ----------------------------------------------------------------------
# is the server/repo in a writable state (i.e., not down for maintenance etc)
unshift @{ $rc{ACCESS_1} }, 'Writable::writable';
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};
# fix some env vars, setup gitolite internal "env" vars (aka rc vars)
# setup some perl/rc/env vars
# ----------------------------------------------------------------------
$ENV{PATH} = "$ENV{GL_BINDIR}:$ENV{PATH}";
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} ||= $$;
@ -127,7 +137,7 @@ sub glrc {
} elsif ( $cmd eq 'current-data-version' ) {
return $current_data_version;
} else {
_die "unknown argument to glrc: $cmd";
_die "unknown argument to glrc: '$cmd'";
}
}
@ -151,17 +161,42 @@ sub query_rc {
exit 0;
}
my @res = map { $rc{$_} } grep { $rc{$_} } @vars;
print join( "\t", @res ) . ( $nonl ? '' : "\n" ) if not $quiet and @res;
# shell truth
exit 0 if @res;
exit 1;
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 ("$rc{GL_ADMIN_BASE}/VERSION") {
for ("$ENV{GL_BINDIR}/VERSION") {
$version = slurp($_) if -r $_;
}
chomp($version);
@ -173,7 +208,7 @@ sub trigger {
if ( exists $rc{$rc_section} ) {
if ( ref( $rc{$rc_section} ) ne 'ARRAY' ) {
_die "$rc_section section in rc file is not a perl list";
_die "'$rc_section' section in rc file is not a perl list";
} else {
for my $s ( @{ $rc{$rc_section} } ) {
my ( $pgm, @args ) = split ' ', $s;
@ -181,13 +216,13 @@ sub trigger {
if ( my ( $module, $sub ) = ( $pgm =~ /^(.*)::(\w+)$/ ) ) {
require Gitolite::Triggers;
trace(1, 'trigger', $module, $sub, @args, $rc_section, @_ );
trace( 1, 'trigger', $module, $sub, @args, $rc_section, @_ );
Gitolite::Triggers::run( $module, $sub, @args, $rc_section, @_ );
} else {
$pgm = "$ENV{GL_BINDIR}/triggers/$pgm";
$pgm = _which("triggers/$pgm", 'x');
_warn("skipped command '$s'"), next if not -x $pgm;
_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!
}
@ -198,25 +233,56 @@ sub trigger {
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] <list of rc variables>
gitolite query-rc [-n] [-q] rc-variable
-a print all variables and values
-n do not append a newline
-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)
Example:
Query the rc hash. Second and subsequent arguments dig deeper into the hash.
The examples are for the default configuration; yours may be different.
gitolite query-rc GL_ADMIN_BASE UMASK
# prints "/home/git/.gitolite<tab>0077" or similar
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 known variables and values, one per line
Note: '-q' is best used with only one variable.
# 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 {
@ -247,8 +313,21 @@ __DATA__
# (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
@ -256,9 +335,9 @@ __DATA__
# 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 cpu-time and desc commands for perl and shell samples.
# look at the info and desc commands for perl and shell samples.
# used by the cpu-time command
# used by the CpuTime trigger
# DISPLAY_CPU_TIME => 1,
# CPU_TIME_WARN_LIMIT => 0.1,
# used by the desc command
@ -282,10 +361,14 @@ __DATA__
COMMANDS =>
{
'help' => 1,
'info' => 1,
'desc' => 1,
# 'fork' => 1,
'info' => 1,
# 'mirror' => 1,
'perms' => 1,
# 'sskm' => 1,
'writable' => 1,
# 'D' => 1,
},
# comment out or uncomment as needed
@ -293,6 +376,17 @@ __DATA__
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
@ -302,13 +396,11 @@ __DATA__
],
# comment out or uncomment as needed
# these will run in sequence at the start, before a git operation has started
# these will run in sequence just before the actual git command is invoked
PRE_GIT =>
[
# if you use this, make this the first item in the list
# 'renice 10',
# see docs ("list of non-core programs shipped") for details
# 'Mirroring::pre_git',
# 'partial-copy',
],
@ -319,15 +411,21 @@ __DATA__
],
# comment out or uncomment as needed
# these will run in sequence at the end, after a git operation has ended
# these will run in sequence after the git command returns
POST_GIT =>
[
# if you use this, make this the last item in the list
# 'cpu-time',
# 'Mirroring::post_git',
# 'CpuTime::post_git',
],
# comment out or uncomment as needed
# these will run in sequence after a new wild repo is created
# 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',

View File

@ -6,19 +6,30 @@ package Gitolite::Setup;
=for args
Usage: gitolite setup [<option>]
-pk, --pubkey <file> pubkey file name
Setup gitolite, compile conf, run the POST_COMPILE trigger (see rc file) and
propagate hooks.
Setup gitolite, compile conf, and fixup hooks. The pubkey is required on the
first run.
-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:
- 'gitolite setup': fix up hooks if you brought in repos from outside, or if
someone has been playing around with the hooks and may have deleted some.
- 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
- 'gitolite setup -pk YourName.pub': replace keydir/YourName.pub and
recompile/push. Useful if you lost your key. In fact you can do this for
any key in keydir (but not in subdirectories).
=cut
# ----------------------------------------------------------------------
@ -40,12 +51,15 @@ use warnings;
# ----------------------------------------------------------------------
sub setup {
my ( $admin, $pubkey, $argv ) = args();
setup_glrc();
setup_gladmin( $admin, $pubkey, $argv );
my ( $admin, $pubkey, $h_only, $argv ) = args();
_system("$ENV{GL_BINDIR}/gitolite compile");
_system("$ENV{GL_BINDIR}/gitolite trigger POST_COMPILE");
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
}
@ -55,30 +69,33 @@ sub setup {
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,
'help|h' => \$help,
'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";
$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 || '', $argv );
return ( $admin || '', $pubkey || '', $h_only || 0, $argv );
}
sub setup_glrc {
@ -87,7 +104,7 @@ sub setup_glrc {
sub setup_gladmin {
my ( $admin, $pubkey, $argv ) = @_;
_die "no existing conf file found, '-a' required"
_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
@ -102,9 +119,6 @@ sub setup_gladmin {
_mkdir( $rc{GL_ADMIN_BASE} );
_chdir( $rc{GL_ADMIN_BASE} );
tsh_try("cd \$GL_BINDIR; git describe --tags --long --dirty=-dt 2>/dev/null")
and _print( "VERSION", tsh_text() );
_mkdir("conf");
_mkdir("logs");
my $conf;
@ -138,8 +152,8 @@ sub setup_gladmin {
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 'gl-setup $argv'")
or die "setup failed to commit to the admin repo";
or tsh_try("git commit -am 'gitolite setup $argv'")
or _die "setup failed to commit to the admin repo";
delete $ENV{GIT_WORK_TREE};
}

View File

@ -8,15 +8,18 @@ package Gitolite::Test;
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;
@ -25,6 +28,7 @@ BEGIN {
*{'try'} = \&Tsh::try;
*{'put'} = \&Tsh::put;
*{'text'} = \&Tsh::text;
*{'lines'} = \&Tsh::lines;
*{'cmp'} = \&Tsh::cmp;
}
@ -33,6 +37,12 @@ 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=/
@ -43,14 +53,15 @@ try "
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; /6b18ec2ab0f765122ec133959b36c57f77d4565c/
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}/src/gitolite $ENV{PWD}/t/glt ~/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\"
@ -99,4 +110,12 @@ sub confadd {
put "|cat >> conf/$file", $string;
}
sub md5sum {
my $out = '';
for my $file (@_) {
$out .= md5_hex(slurp($file)) . " $file\n";
}
return $out;
}
1;

View File

@ -261,7 +261,12 @@ sub rc_lines {
$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+(.*))?$) );
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 ) {
@ -333,7 +338,7 @@ sub _sh {
# TODO: switch to IPC::Open3 or something...?
dbg( 4, " running: ( $cmd ) 2>&1" );
$text = `( $cmd ) 2>&1; echo -n RC=\$?`;
$text = `( $cmd ) 2>&1; /bin/echo -n RC=\$?`;
$lec = $cmd;
dbg( 4, " results:\n$text" );
@ -473,9 +478,9 @@ sub fail {
}
sub cmp {
# compare input string with text()
my $text = text();
my $in = shift;
# compare input string with second input string or text()
my $in = shift;
my $text = ( @_ ? +shift : text() );
if ( $text eq $in ) {
ok();
@ -583,7 +588,7 @@ sub dummy_commits {
test_tick();
next;
}
my $ts = ( $tick ? localtime($tick) : localtime() );
my $ts = ( $tick ? gmtime( $tick + 19800 ) : gmtime() );
_sh("echo $f at $ts >> $f && git add $f && git commit -m '$f at $ts'");
}
}

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

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