Full Code of sitaramc/gitolite for AI

master 4d2611d6e256 cached
186 files
682.3 KB
214.8k tokens
1 requests
Download .txt
Showing preview only (728K chars total). Download the full file or copy to clipboard to get everything.
Repository: sitaramc/gitolite
Branch: master
Commit: 4d2611d6e256
Files: 186
Total size: 682.3 KB

Directory structure:
gitextract_ay9ki2ef/

├── CHANGELOG
├── CONTRIBUTING
├── COPYING
├── README.markdown
├── check-g2-compat
├── contrib/
│   ├── commands/
│   │   ├── compile-1
│   │   └── ukm
│   ├── hooks/
│   │   └── repo-specific/
│   │       └── save-push-signatures
│   ├── lib/
│   │   ├── Apache/
│   │   │   └── gitolite.conf
│   │   └── Gitolite/
│   │       └── Triggers/
│   │           └── RedmineUserAlias.pm
│   ├── t/
│   │   └── ukm.t
│   ├── triggers/
│   │   ├── IP-check
│   │   └── file_mirror
│   ├── utils/
│   │   ├── ad_groups.sh
│   │   ├── gitolite-local
│   │   ├── ipa_groups.pl
│   │   ├── ldap_groups.sh
│   │   ├── rc-format-v3.4
│   │   └── testconf
│   └── vim/
│       ├── indent/
│       │   └── gitolite.vim
│       └── syntax/
│           └── gitolite.vim
├── convert-gitosis-conf
├── install
├── src/
│   ├── VREF/
│   │   ├── COUNT
│   │   ├── EMAIL-CHECK
│   │   ├── FILETYPE
│   │   ├── MAX_NEWBIN_SIZE
│   │   ├── MERGE-CHECK
│   │   ├── NAME_NC
│   │   ├── VOTES
│   │   ├── lock
│   │   ├── partial-copy
│   │   └── refex-expr
│   ├── commands/
│   │   ├── 1plus1
│   │   ├── D
│   │   ├── access
│   │   ├── compile-template-data
│   │   ├── config
│   │   ├── create
│   │   ├── creator
│   │   ├── desc
│   │   ├── fork
│   │   ├── git-annex-shell
│   │   ├── git-config
│   │   ├── help
│   │   ├── htpasswd
│   │   ├── info
│   │   ├── list-dangling-repos
│   │   ├── lock
│   │   ├── mirror
│   │   ├── motd
│   │   ├── newbranch
│   │   ├── option
│   │   ├── owns
│   │   ├── perms
│   │   ├── print-default-rc
│   │   ├── push
│   │   ├── readme
│   │   ├── rsync
│   │   ├── sshkeys-lint
│   │   ├── sskm
│   │   ├── sudo
│   │   ├── svnserve
│   │   ├── symbolic-ref
│   │   ├── who-pushed
│   │   └── writable
│   ├── gitolite
│   ├── gitolite-shell
│   ├── lib/
│   │   └── Gitolite/
│   │       ├── Cache.pm
│   │       ├── Common.pm
│   │       ├── Conf/
│   │       │   ├── Explode.pm
│   │       │   ├── Load.pm
│   │       │   ├── Store.pm
│   │       │   └── Sugar.pm
│   │       ├── Conf.pm
│   │       ├── Easy.pm
│   │       ├── Hooks/
│   │       │   ├── PostUpdate.pm
│   │       │   └── Update.pm
│   │       ├── Rc.pm
│   │       ├── Setup.pm
│   │       ├── Test/
│   │       │   └── Tsh.pm
│   │       ├── Test.pm
│   │       ├── Triggers/
│   │       │   ├── Alias.pm
│   │       │   ├── AutoCreate.pm
│   │       │   ├── CpuTime.pm
│   │       │   ├── Kindergarten.pm
│   │       │   ├── Mirroring.pm
│   │       │   ├── Motd.pm
│   │       │   ├── RefexExpr.pm
│   │       │   ├── RepoUmask.pm
│   │       │   ├── Shell.pm
│   │       │   ├── TProxy.pm
│   │       │   └── Writable.pm
│   │       └── Triggers.pm
│   ├── syntactic-sugar/
│   │   ├── continuation-lines
│   │   ├── keysubdirs-as-groups
│   │   ├── macros
│   │   └── refex-expr
│   └── triggers/
│       ├── bg
│       ├── expand-deny-messages
│       ├── partial-copy
│       ├── post-compile/
│       │   ├── create-with-reference
│       │   ├── ssh-authkeys
│       │   ├── ssh-authkeys-shell-users
│       │   ├── ssh-authkeys-split
│       │   ├── update-description-file
│       │   ├── update-git-configs
│       │   ├── update-git-daemon-access-list
│       │   ├── update-gitweb-access-list
│       │   └── update-gitweb-daemon-from-options
│       ├── renice
│       ├── repo-specific-hooks
│       ├── set-default-roles
│       └── upstream
└── t/
    ├── 0-me-first.t
    ├── C-vs-C.t
    ├── README
    ├── access.t
    ├── all-yall.t
    ├── basic.t
    ├── branch-perms.t
    ├── daemon-gitweb-via-perms.t
    ├── deleg-1.t
    ├── deleg-2.t
    ├── deny-create.t
    ├── deny-rules-2.t
    ├── deny-rules.t
    ├── easy.t
    ├── fedora-root-smart-http-test-setup
    ├── fork.t
    ├── git-config.t
    ├── gitolite-receive-pack
    ├── gitolite-upload-pack
    ├── glt
    ├── hostname.t
    ├── include-subconf.t
    ├── info-json.t
    ├── info.t
    ├── invalid-refnames-filenames.t
    ├── keys/
    │   ├── admin
    │   ├── admin.pub
    │   ├── config
    │   ├── u1
    │   ├── u1.pub
    │   ├── u2
    │   ├── u2.pub
    │   ├── u3
    │   ├── u3.pub
    │   ├── u4
    │   ├── u4.pub
    │   ├── u5
    │   ├── u5.pub
    │   ├── u6
    │   └── u6.pub
    ├── listers.t
    ├── manjaro-root-smart-http-test-setup
    ├── merge-check.t
    ├── mirror-test
    ├── mirror-test-rc
    ├── mirror-test-setup.sh
    ├── mirror-test-ssh-config
    ├── partial-copy.t
    ├── perm-default-roles.t
    ├── perm-roles.t
    ├── perms-groups.t
    ├── personal-branches.t
    ├── reference.t
    ├── refex-expr-test-1
    ├── refex-expr-test-2
    ├── refex-expr-test-3
    ├── refex-expr-test-9
    ├── repo-specific-hooks.t
    ├── reset
    ├── rule-seq.t
    ├── sequence.t
    ├── smart-http
    ├── smart-http.root-setup
    ├── ssh-authkeys.t
    ├── ssh-basic.t
    ├── templates.t
    ├── vrefs-1.t
    ├── vrefs-2.t
    ├── wild-1.t
    ├── wild-2.t
    ├── writable.t
    └── z-end.t

================================================
FILE CONTENTS
================================================

================================================
FILE: CHANGELOG
================================================
2025-07-18  v3.6.14 only one important fix:

                    detect HEAD name in admin repo (don't require it be "master")

2023-07-14  v3.6.13 only one important fix:

                    remove the "description" file on new repos, to fix a bug
                    created by an optimisation made 6 years ago in 3.6.8 (and
                    was caught only in 2022 or so!)

2020-08-04  v3.6.12 mirroring terminology changes

                    install script can now modify #! lines when using a custom
                    perl executable

                    'config' user command allows for config values with spaces
                    in them

                    finally added notes in "t/README" on testing http mode and
                    mirroring, with pre-build helpers for Fedora and Manjaro

                    ...plus various bug fixes

2019-01-08  v3.6.11 fix security issue in 'rsync' (bundle helper); see commit
                    5df2b81 for more

2018-09-30  v3.6.10 fix up boo-boo caused by previous release; see mails on
                    list for details

2018-08-07  v3.6.9  prevent racy access to repos in process of migration to
                    gitolite

                    'info' learns new '-p' option to show only physical repos
                    (as opposed to wild repos)

2018-07-12  v3.6.8  fix bug when deleting *all* hooks for a repo

                    allow trailing slashes in repo names

                    make pre-receive hook driver bail on non-zero exit of a
                    pre-receive hook

                    allow templates in gitolite.conf (new feature)

                    various optimiations

2017-07-02  v3.6.7  allow repo-specific hooks to be organised into
                    subdirectories, and allow the multi-hook driver to be
                    placed in some other location of your choice

                    allow simple test code to be embedded within the
                    gitolite.conf file; see contrib/utils/testconf for how.
                    (This goes on the client side, not on the server)

                    allow syslog "facility" to be changed, from the default of
                    'local0'

                    allow @group names in config values to be expanded; it is
                    replaced with a space separated list of members

2016-09-08  v3.6.6  simple but important fix for a future perl deprecation
                    (perl will be removing "." from @INC in 5.24)

                    'perms' now requires a '-c' to activate batch mode
                    (should not affect interactive use but check your scripts
                    perhaps?)

                    gitolite setup now accepts a '-m' option to supply a
                    custom message (useful when it is used by a script)

2016-02-20  v3.6.5  allow creator check to be bypassed during mirroring

                    handle new style ssh fingerprinting correctly (thanks to
                    Robin Johnson)

                    allow pre-auto-gc as a repo-specific hook

                    optimise mirror pushes for heavily used repos

                    create-with-reference trigger: on repo creation, setup
                    objects/info/alternates for a server side alternate object
                    store.

                    'mirror status all all' prints a list of repos that have
                    *some* error, which is arguably more useful for further
                    action/processing

                    allow incrementally adding more repo-specific hooks

2015-11-01  v3.6.4  a ref-create bug in wild repos was fixed

                    some contrib code related to AD integration, and to
                    redmine user aliases

                    teach Alias.pm a few new tricks

                    remove a race condition in 'create' command that affected
                    the 'default roles' setting

                    make 'who-pushed' more efficient (local push logs, and
                    'tip search')

                    'gitolite query-rc' learns '-d' ('--dump') option

2015-04-26  v3.6.3  allow limited use of 'git config' using the new 'config'
                    command

                    accept openssh 6.8's new fingerprint output format

                    (finally!) allow limited symlinks within ~/repositories;
                    see commit 8e36230 for details

                    perms command now lists available roles

                    minor backward compat breakage: 'perms -l repo' no longer
                    works; see 'perms -h' for new usage

                    allow gitolite-shell to be used as $SHELL (experts only;
                    no support, no docs; see commit 9cd1e37 for details)

                    help with 'git push --signed' using a post-receive hook to
                    adopt push certs into 'refs/push-certs'; for details see
                    contrib/hooks/repo-specific/save-push-signatures

                    new 'transparent proxy' feature for git repos; see
                    src/lib/Gitolite/Triggers/TProxy.pm for details

2014-11-10  v3.6.2  disable ../ everywhere (see mailing list thread for
                    details)

                    VREF/NAME_NC -- like VREF/NAME but for new commits only.
                    Details within src/VREF/NAME_NC.

                    allow gitolite.conf to be tested locally; details within
                    contrib/utils/gitolite-local

2014-06-22  v3.6.1  experimental rc format convertor for "<= 3.3" users who
                    have already upgraded the *code* to ">= v3.4".  Program is
                    in contrib/utils.

                    giving shell access to a few users got a lot easier (see
                    comments in the rc file).

                    allow logging to syslog as well (see comments in the rc
                    file)

                    new 'motd' command

                    redis caching redone and now in core; see
                    http://gitolite.com/gitolite/cache.html

2014-05-09  v3.6    (cool stuff) the access command can now help you debug
                    your rules / understand how a specific access decision was
                    arrived at.

                    mirroring: since mirroring is asynchronous (by default
                    anyway), when a 'git push --mirror' fails, you may not
                    know it unless you look in the log file on the server.
                    Now gitolite captures the info and -- if the word 'fatal'
                    appears anywhere within it, it saves the entire output and
                    prints it to STDERR for anyone who reads or writes the
                    repo on the *master* server, until the error condition
                    clears up.

                    mirroring: allow 'nosync' slaves -- no attempt to
                    automatically push to these slaves will be made.  Instead,
                    you have to manually (or via cron, etc) trigger pushes.

                    (backward compat breakage) the old v2 syntax for
                    specifying gitweb owner and description is no longer
                    supported.

                    macros now allow strings as arguments (thanks to Jason
                    Donenfeld for the idea/problem).

                    the 'info' command can print in JSON format if asked to.

                    repo-specific hooks: now you can specify more than one,
                    and gitolite runs all of them in sequence.

                    new trigger 'expand-deny-messages' to show more details
                    when access is denied.

                    git-annex support is finally in master, yaaay!

                    new 'readme' command, modelled after 'desc'.  Apparently
                    gitweb can use a README.html file in the *bare* repo
                    directory -- who knew!

2013-10-14  v3.5.3  catch undefined groupnames (when possible)

                    mirroring: async push to slaves

                    (some portability fixes)

                    (a couple of contrib scripts - querying IPA based LDAP
                    servers for group membership, and user key management)

                    allow groups in subconf files (this *may* slow down
                    compilation in extreme cases)

                    make adding repo-specific hooks easier (see cust.mkd or
                    cust.html online for docs)

                    smart http now supports git 1.8.2 and above (which changed
                    the protocol requirements a wee bit)

2013-07-10  v3.5.2  allow ENV vars to be set from repo options, for use in
                    triggers and hooks

                    bug-fix: the new set-default-roles feature was being
                    invoked on every run of "perms" and overriding it!

2013-03-24  v3.5    (2 minor backward compat breakages)
                    1.  'DEFAULT_ROLE_PERMS' replaced by per repo
                        'default.roles' option
                    2.  'gitolite list-memberships' now requires a '-r' or a
                        '-u' flag

                    new 'gitolite owns' command (thanks to Kevin Pulo)

2013-03-05  v3.4    new rc file format makes it much easier to enable specific
                    features

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.


================================================
FILE: CONTRIBUTING
================================================
Go to http://gitolite.com/gitolite/index.html#contactsupport for information on
contacting me, the mailing list, and IRC channel.  *Unless you are reporting
what you think is a security issue, I prefer you send to the mailing list,
not to me directly.*

Please DO NOT send messages via github's "issues" system, linkedin
comments/discussion, stackoverflow questions, google+, and any other Web 3.0
"coolness". (The issues system does have an email interface, but it is not a
substitute for email. I can't cc anyone else when I want to, for instance.
Well I can, but any response the original requester then makes using the
website will not get cc-d to the person I cc-d).

Please send patches *via email*, not as github pull requests. Again, if you
think it's a security issue, send it directly to my gmail address, but
otherwise please send it to the mailing list, so others can see it and comment
on it.

The preferred format is the files created by git-format-patch, as attachments.
However, if your repo has a public clone URL, you can make a new branch just
for this fix, and send the repo URL and branch name to the mailing list.

(If you do send me a github pull request, I may take it if it's a trivial
patch, but otherwise I'll ask you to close the pull request, then read
this URL for how to send me the patch.)


================================================
FILE: COPYING
================================================
                    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.


================================================
FILE: README.markdown
================================================
Gitolite README
===============

## about this README

**(Github-users: click the "wiki" link before sending me anything via github.)**

**This is a minimal README for gitolite**, so you can quickly get started with:

*   installing gitolite on a fresh userid on a Unix(-like) machine, using ssh
*   learning enough to do some basic access control

**For anything more, you need to look at the complete documentation, at:
<http://gitolite.com/gitolite>**.  Please go there for what/why/how, concepts,
background, troubleshooting, more details on what is covered here, advanced
features not covered here, migration from older gitolite, running gitolite
over http (rather than ssh), and many more topics.

<!-- --------------------------------------------------------------------- -->

## Assumptions

*   You are familiar with:
    *   OS: at least one Unix-like OS
    *   ssh: ssh, ssh keys, ssh authorized keys file
    *   git: basic use of git, bare and non-bare remotes

*   You are setting up a fresh, ssh-based, installation of gitolite on a Unix
    machine of some sort.

*   You have root access, or someone has created a userid called "git" for you
    to use and given you a password for it.  This is a brand new userid (or
    you have deleted everything but `.bashrc` and similar files to make it
    look like one!)

*   If your server is not connected to the internet, you know how to clone the
    gitolite source code by using some in-between server or "git bundle".

<!-- --------------------------------------------------------------------- -->

## Installation and setup

### server requirements

*   any unix system
*   sh
*   git 1.6.6 or later
*   perl 5.8.8 or later
*   openssh 5.0 or later
*   a dedicated userid to host the repos (in this document, we assume it is
    "git", but it can be anything; substitute accordingly)
*   this user id does NOT currently have any ssh pubkey-based access
    *   ideally, this user id has shell access ONLY by "su - git" from some
        other userid on the same server (this ensure minimal confusion for ssh
        newbies!)

### steps to install

First, prepare the ssh key:

*   login to "git" on the server
*   make sure `~/.ssh/authorized_keys` is empty or non-existent
*   make sure your ssh public key from your workstation has been copied as
    $HOME/YourName.pub

Next, install gitolite by running these commands:

    git clone https://github.com/sitaramc/gitolite
    mkdir -p $HOME/bin
    gitolite/install -to $HOME/bin

Finally, setup gitolite with yourself as the administrator:

    gitolite setup -pk YourName.pub

If the last command doesn't run perhaps "bin" is not in your "PATH". You can
either add it, or just run:

    $HOME/bin/gitolite setup -pk YourName.pub

If you get any other errors please refer to the online documentation whose URL
was given at the top of this file.

## 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 went wrong.**.  Go hit
>   the link for the complete documentation earlier in this file.

>   -------------------------------------------------------------------------

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

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".

## access rule examples

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

Here's what these 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.

Please see the main documentation linked above for all the gory details, as
well as more features and examples.

## 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.  Running

    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".

<!-- --------------------------------------------------------------------- -->

## LICENSE

# contact and support

Please see <http://gitolite.com/gitolite/#contactsupport> for mailing list and IRC
info.

# license

The gitolite software is copyright Sitaram Chamarty and is licensed under the
GPL v2; please see the file called COPYING in the source distribution.

Please see <http://gitolite.com/gitolite/#license> for more.

>   -------------------------------------------------------------------------

>   **NOTE**: GIT is a trademark of Software Freedom Conservancy and my use of
>   "Gitolite" is under license.

>   -------------------------------------------------------------------------


================================================
FILE: check-g2-compat
================================================
#!/usr/bin/perl

use Cwd;

my $h  = $ENV{HOME};
my $rc = "$h/.gitolite.rc";
my %count;

intro();

msg( FATAL => "no rc file found; do you even *have* g2 running?" ) if not -f $rc;
do $rc;
unless ( $return = do $rc ) {
    msg( FATAL => "couldn't parse $rc: $@" ) if $@;
    msg( FATAL   => "couldn't do $rc: $!" ) unless defined $return;
    msg( WARNING => "couldn't run $rc" )    unless $return;
}

print "checking rc file...\n";
rc_basic();
rest_of_rc();
print "\n";

print "checking conf file(s)...\n";
conf();
print "\n";

print "checking repos...\n";
repo();
print "\n";

print "...all done...\n";

# ----------------------------------------------------------------------

sub intro {
    msg( INFO => "This program only checks for uses that make the new g3 completely unusable" );
    msg( ''   => "or that might end up giving *more* access to someone if migrated as-is." );
    msg( ''   => "It does NOT attempt to catch all the differences described in the docs." );
    msg( '', '' );
    msg( INFO => "'see docs' usually means the pre-migration checklist in" );
    msg( '',  => "'g2migr.html'; to get there, start from the main migration" );
    msg( '',  => "page at http://gitolite.com/gitolite/migr.html" );
    msg( '', '' );
}

sub rc_basic {
    msg( FATAL => "GL_ADMINDIR in the wrong place -- aborting; see docs" ) if $GL_ADMINDIR ne "$h/.gitolite";
    msg( NOTE => "GL_ADMINDIR is in the right place; assuming you did not mess with" );
    msg( '', "GL_CONF, GL_LOGT, GL_KEYDIR, and GL_CONF_COMPILED" );
    msg( FATAL => "REPO_BASE in the wrong place -- aborting; see docs" ) if $REPO_BASE ne "$h/repositories" and $REPO_BASE ne "repositories";
# ( abs or rel both ok)
}

sub rest_of_rc {
    msg( SEVERE  => "GIT_PATH found; see docs" )                          if $GIT_PATH;
    msg( SEVERE  => "GL_ALL_INCLUDES_SPECIAL found; see docs" )           if $GL_ALL_INCLUDES_SPECIAL;
    msg( SEVERE  => "GL_NO_CREATE_REPOS not yet implemented" )            if $GL_NO_CREATE_REPOS;
    msg( SEVERE  => "rsync not yet implemented" )                         if $RSYNC_BASE;
    msg( WARNING => "ADMIN_POST_UPDATE_CHAINS_TO found; see docs" )       if $ADMIN_POST_UPDATE_CHAINS_TO;
    msg( WARNING => "GL_NO_DAEMON_NO_GITWEB found; see docs" )            if $GL_NO_DAEMON_NO_GITWEB;
    msg( WARNING => "GL_NO_SETUP_AUTHKEYS found; see docs" )              if $GL_NO_SETUP_AUTHKEYS;
    msg( WARNING => "UPDATE_CHAINS_TO found; see docs" )                  if $UPDATE_CHAINS_TO;
    msg( WARNING => "GL_ADC_PATH found; see docs" )                       if $GL_ADC_PATH;
    msg( WARNING => "non-default GL_WILDREPOS_PERM_CATS found" ) if $GL_WILDREPOS_PERM_CATS ne 'READERS WRITERS';
}

sub conf {
    chdir($h);
    chdir($GL_ADMINDIR);

    my $conf = `find . -name "*.conf" | xargs cat`;
    msg( "SEVERE", "NAME rules; see docs" )                    if $conf =~ m(NAME/);
    msg( "SEVERE", "subconf command in admin repo; see docs" ) if $conf =~ m(NAME/conf/fragments);
    msg( "SEVERE", "mirroring used; see docs" )                if $conf =~ m(config +gitolite\.mirror\.);
}

sub repo {
    chdir($h);
    chdir($REPO_BASE);
    my @creater = `find . -name gl-creater`;
    if (@creater) {
        msg( WARNING => "found " . scalar(@creater) . " gl-creater files; see docs" );
    }

    my @perms = `find . -name gl-perms | xargs egrep -l -w R\\|RW`;
    if (@perms) {
        msg( WARNING => "found " . scalar(@perms) . " gl-perms files with R or RW; see docs" );
    }
}

sub msg {
    my ( $type, $text ) = @_;
    print "$type" if $type;
    print "\t$text\n";
    exit 1 if $type eq 'FATAL';

    $count{$type}++ if $type;
}


================================================
FILE: contrib/commands/compile-1
================================================
#!/usr/bin/perl -s
use strict;
use warnings;

# DESCRIPTION:

#   This program is meant to re-compile the access rules (and 'config' or
#   'option' lines) of exactly ONE actual repo (i.e., not a repo group or a
#   repo pattern).

# MOTIVATION:

#   Fedora has a huge number of repos, as well as lot of churn in permissions.
#   The combination of having a large conf *and* frequent compiles were not
#   working out, hence this solution.  Not sure if any others have such a
#   situation, so it's a standalone program, separate from "core" gitolite,
#   shipped in "contrib" instead of "src".

# SETUP:

#   It expects to run as a gitolite sub-command, which means you will need to
#   copy it from contrib to src/commands, or the equivalent location inside
#   LOCAL_CODE; see non-core.html in the docs for details.

# INVOCATION:

#   It takes one argument: the name of a file that contains the new ruleset
#   you want to use.  (This cannot be STDIN or "-" or something).

#   example:
#
#       gitolite compile-1 <file-containing-rules-for-exactly-one-repo>

# WARNING:

#   If the main gitolite.conf changes significantly (specifically, if the
#   number of effective rules in it increase quite a bit), you may have to run
#   this command on ALL repos to update their individual gl-conf files.
#
#   (TBD: explain this in more concrete terms)

# ----------------------------------------------------------------------
# THERE IS NO ERROR CHECKING ON THE WARNING ABOVE, NOR ON THE ASSUMPTIONS AND
# REQUIREMENTS BELOW.  PLEASE USE CAREFULLY!
# ----------------------------------------------------------------------

# ASSUMPTIONS/REQUIREMENTS:

#   The file given must contain exactly one 'repo' line, with exactly one repo
#   name, followed by the rules, configs, and options for that repo in the
#   normal gitolite.conf syntax.

#   The file must not have any group definitions, though it may use group
#   definitions already setup in the main gitolite.conf file.

#   Rules for this repo need not be already defined in the main gitolite.conf.
#   If they are, they will cease to have any effect once you run this command
#   - only the rules you supply in the file passed to this command will apply,
#   and they will be considered to be placed at the end of gitolite.conf.

#   If the repo does not exist, it must be first created using:
#
#       GL_USER=admin gitolite create <reponame>
#
#   where <reponame> is the gitolite-style name (i.e., "foo", not "foo.git" or
#   "~/repositories/foo" or "~/repositories/foo.git")
#
#   This, of course, requires the main gitolite.conf to have the following
#   lines at the top:
#
#       repo [A-Za-z].*
#           C   =   admin

#   Any change to the main gitolite.conf is followed by a full 'gitolite
#   compile'; i.e., ~/.gitolite/conf/gitolite.conf-compiled.pm, the main
#   "compiled" conf file, is consistent with the latest gitolite.conf.

use 5.10.0;
use Data::Dumper;

use lib $ENV{GL_LIBDIR};
use Gitolite::Rc;
use Gitolite::Common;
use Gitolite::Conf;
use Gitolite::Conf::Store;
use Gitolite::Conf::Sugar;

my ($cf, $repo) = args();       # conffile from @ARGV, repo from first line of conffile
my $startseq = getseq();        # get the starting sequence number by looking in the (common) compiled conf file
parse_and_store($cf, $repo);    # parse the ruleset and write out just the gl-conf file
                                # (this is the only part that uses core gitolite functions)
update_seq($repo, $startseq);   # update gl-conf with adjusted sequence numbers

exit 0;

# ----------------------------------------------------------------------

sub args {
    my $cf = shift @ARGV or _die "need conffile";
    $cf = $ENV{PWD} . "/" . $cf unless $cf =~ m(^/);

    my $t = slurp($cf);
    _die "bad conf file" unless $t =~ /^\s*repo\s+(\S+)\s*$/m;
    my $repo = $1;

    return ($cf, $repo);
}

sub getseq {
    my @main_cc = slurp "$rc{GL_ADMIN_BASE}/conf/gitolite.conf-compiled.pm";
    my $max = 0;
    for (@main_cc) {
        $max = $1 if m/^ +(\d+),$/ and $max < $1;
    }

    return $max;
}

sub parse_and_store {
    my ($cf, $repo) = @_;

    parse(sugar($cf));
    _chdir( $rc{GL_REPO_BASE} );
    Gitolite::Conf::Store::store_1($repo);
}

sub update_seq {
    my ($repo, $startseq) = @_;

    _chdir("$rc{GL_REPO_BASE}/$repo.git");
    my $text = slurp("gl-conf");

    $startseq+=1000;
    # just for safety, in case someone adds a few rules to the main conf later, but neglects to update repo confs

    $text =~ s/^( +)(\d+),$/"$1" . ($2+$startseq) . ","/gme;

    _print("gl-conf", $text);
}


================================================
FILE: contrib/commands/ukm
================================================
#!/usr/bin/perl
use strict;
use warnings;

use lib $ENV{GL_LIBDIR};
use Gitolite::Rc;
use Gitolite::Common;
use Gitolite::Easy;

=for usage
Usage for this command is not that simple. Please read the full
documentation in
https://github.com/sitaramc/gitolite-doc/blob/master/docs/contrib/ukm.mkd
or online at http://gitolite.com/gitolite/contrib/ukm.html.
=cut

usage() if @ARGV and $ARGV[0] eq '-h';

# Terms used in this file.
# pubkeypath: the (relative) filename of a public key starting from
#   gitolite-admin/keydir. Examples: alice.pub, foo/bar/alice.pub,
#   alice@home.pub, foo/alice@laptop.pub. You get more examples, if you
#   replace "alice" by "bob@example.com".
# userid: computed from a pubkeypath by removing any directory
#   part, the '.pub' extension and the "old-style" @NAME classifier.
#   The userid identifies a user in the gitolite.conf file.
# keyid: an identifier for a key given on the command line.
#   If the script is called by one of the super_key_managers, then the
#   keyid is the pubkeypath without the '.pub' extension. Otherwise it
#   is the userid for a guest.
#   The keyid is normalized to lowercase letters.

my $rb = $rc{GL_REPO_BASE};
my $ab = $rc{GL_ADMIN_BASE};

# This will be the subdirectory under "keydir" in which the guest
# keys will be stored. To prevent denial of service, this directory
# should better start with 'zzz'.
# The actual value can be set through the GUEST_DIRECTORY resource.
# WARNING: If this value is changed you must understand the consequences.
#          There will be no support if guestkeys_dir is anything else than
#          'zzz/guests'.
my $guestkeys_dir = 'zzz/guests';

# A guest key cannot have arbitrary names (keyid). Only keys that do *not*
# match $forbidden_guest_pattern are allowed. Super-key-managers can add
# any keyid.

# This is the directory for additional keys of a self key manager.
my $selfkeys_dir = 'zzz/self';
# There is no flexibility for selfkeys. One must specify a keyid that
# matches the regular expression '^@[a-z0-9]+$'. Note that all keyids
# are transformed to lowercase before checking.
my $required_self_pattern = qr([a-z0-9]+);
my $selfkey_management = 0; # disable selfkey managment

# For guest key managers the keyid must pass two tests.
#   1) It must match the $required_guest_pattern regular expression.
#   2) It must not match the $forbidden_guest_pattern regular expression.
# Default for $forbidden_guest_pattern is qr(.), i.e., every keyid is
# forbidden, or in other words, only the gitolite-admin can manage keys.
# Default for $required_guest_pattern is such that the keyid must look
# like an email address, i.e. must have exactly one @ and at least one
# dot after the @.
# Just setting 'ukm' => 1 in .gitolite.rc only allows the super-key-managers
# (i.e., only the gitolite admin(s)) to manage keys.
my $required_guest_pattern =
    qr(^[0-9a-z][-0-9a-z._+]*@[-0-9a-z._+]+[.][-0-9a-z._+]+$);
my $forbidden_guest_pattern = qr(.);

die "The command 'ukm' is not enabled.\n" if ! $rc{'COMMANDS'}{'ukm'};

my $km = $rc{'UKM_CONFIG'};
if(ref($km) eq 'HASH') {
    # If not set we only allow keyids that look like emails
    my $rgp = $rc{'UKM_CONFIG'}{'REQUIRED_GUEST_PATTERN'} || '';
    $required_guest_pattern = qr(^($rgp)$) if $rgp;
    $forbidden_guest_pattern = $rc{'UKM_CONFIG'}{'FORBIDDEN_GUEST_PATTERN'}
                            || $forbidden_guest_pattern;
    $selfkey_management = $rc{'UKM_CONFIG'}{'SELFKEY_MANAGEMENT'} || 0;
}

# get the actual userid
my $gl_user = $ENV{GL_USER};
my $super_key_manager = is_admin(); # or maybe is_super_admin() ?

# save arguments for later
my $operation = shift || 'list';
my $keyid     = shift || '';
$keyid = lc $keyid; # normalize to lowercase ids

my ($zop, $zfp, $zselector, $zuser) = get_pending($gl_user);
# The following will only be true if a selfkey manager logs in to
# perform a pending operation.
my $pending_self = ($zop ne '');

die "You are not a key manager.\n"
    unless $super_key_manager || $pending_self
           || in_group('guest-key-managers')
           || in_group('self-key-managers');

# Let's deal with the pending user first. The only allowed operations
# that are to confirm the add operation with the random code
# that must be provided via stdin or to undo a pending del operation.
if ($pending_self) {
    pending_user($gl_user, $zop, $zfp, $zselector, $zuser);
    exit;
}

my @available_operations = ('list','add','del');
die "unknown ukm subcommand: $operation\n"
    unless grep {$operation eq $_} @available_operations;

# get to the keydir
_chdir("$ab/keydir");

# Note that the program warns if it finds a fingerprint that maps to
# different userids.
my %userids = (); # mapping from fingerprint to userid
my %fingerprints = (); # mapping from pubkeypath to fingerprint
my %pubkeypaths = (); # mapping from userid to pubkeypaths
                      # note that the result is a list of pubkeypaths

# Guest keys are managed by people in the @guest-key-managers group.
# They can only add/del keys in the $guestkeys_dir directory. In fact,
# the guest key manager $gl_user has only access to keys inside
# %guest_pubkeypaths.
my %guest_pubkeypaths = (); # mapping from userid to pubkeypath for $gl_user

# Self keys are managed by people in the @self-key-managers group.
# They can only add/del keys in the $selfkeys_dir directory. In fact,
# the self key manager $gl_user has only access to keys inside
# %self_pubkeypaths.
my %self_pubkeypaths = ();

# These are the keys that are managed by a super key manager.
my @all_pubkeypaths = `find . -type f -name "*.pub" 2>/dev/null | sort`;

for my $pubkeypath (@all_pubkeypaths) {
    chomp($pubkeypath);
    my $fp = fingerprint($pubkeypath);
    $fingerprints{$pubkeypath} = $fp;
    my $userid = get_userid($pubkeypath);
    my ($zop, $zfp, $zselector, $zuser) = get_pending($userid);
    $userid = $zuser if $zop;
    if (! defined $userids{$fp}) {
        $userids{$fp} = $userid;
    } else {
        warn "key $fp is used for different user ids\n"
            unless $userids{$fp} eq $userid;
    }
    push @{$pubkeypaths{$userid}}, $pubkeypath;
    if ($pubkeypath =~ m|^./$guestkeys_dir/([^/]+)/[^/]+\.pub$|) {
        push @{$guest_pubkeypaths{$userid}}, $pubkeypath if $gl_user eq $1;
    }
    if ($pubkeypath =~ m|^./$selfkeys_dir/([^/]+)/[^/]+\.pub$|) {
        push @{$self_pubkeypaths{$userid}}, $pubkeypath if $gl_user eq $1;
    }
}

###################################################################
# do stuff according to the operation
###################################################################

if ( $operation eq 'list' ) {
    list_pubkeys();
    print "\n\n";
    exit;
}

die "keyid required\n" unless $keyid;
die "Not allowed to use '..' in keyid.\n" if $keyid =~ /\.\./;

if ( $operation eq 'add' ) {
    if ($super_key_manager) {
        add_pubkey($gl_user, "$keyid.pub", safe_stdin());
    } elsif (selfselector($keyid)) {
        add_self($gl_user, $keyid, safe_stdin());
    } else {
        # assert ingroup('guest-key-managers');
        add_guest($gl_user, $keyid, safe_stdin());
    }
} elsif ( $operation eq 'del' ) {
    if ($super_key_manager) {
        del_super($gl_user, "$keyid.pub");
    } elsif (selfselector($keyid)) {
        del_self($gl_user, $keyid);
    } else {
        # assert ingroup('guest-key-managers');
        del_guest($gl_user, $keyid);
    }
}

exit;


###################################################################
# only function definitions are following
###################################################################

# make a temp clone and switch to it
our $TEMPDIR;
BEGIN { $TEMPDIR = `mktemp -d -t tmp.XXXXXXXXXX`; chomp($TEMPDIR) }
END { my $err = $?; `/bin/rm -rf $TEMPDIR`; $? = $err; }

sub cd_temp_clone {
    chomp($TEMPDIR);
    hushed_git( "clone", "$rb/gitolite-admin.git", "$TEMPDIR/gitolite-admin" );
    chdir("$TEMPDIR/gitolite-admin");
    my $ip = $ENV{SSH_CONNECTION};
    $ip =~ s/ .*//;
    my ($zop, $zfp, $zselector, $zuser) = get_pending($ENV{GL_USER});
    my $email = $zuser;
    $email .= '@' . $ip  unless $email =~ m(@);
    my $name = $zop ? "\@$zselector" : $zuser;
    # Record the keymanager in the gitolite-admin repo as author of the change.
    hushed_git( "config", "user.email", "$email" );
    hushed_git( "config", "user.name",  "'$name from $ip'" );
}

# compute the fingerprint from the full path of a pubkey file
sub fingerprint {
    my ($fp, $output) = ssh_fingerprint_file(shift);
    # Do not print the output of $output to an untrusted destination.
    die "does not seem to be a valid pubkey\n" unless $fp;
    return $fp;
}


# Read one line from STDIN and return it.
#  If no data is available on STDIN after one second, the empty string
# is returned.
# If there is more than one line or there was an error in reading, the
# function dies.
sub safe_stdin {
    use IO::Select;
    my $s=IO::Select->new(); $s->add(\*STDIN);
    return '' unless $s->can_read(1);
    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;
}

# call git, be quiet
sub hushed_git {
    system("git " . join(" ", @_) . ">/dev/null 2>/dev/null");
}

# Extract the userid from the full path of the pubkey file (relative
# to keydir/ and including the '.pub' extension.
sub get_userid {
    my ($u) = @_; # filename of pubkey relative to keydir/.
    $u =~ s(.*/)();                # foo/bar/baz.pub -> baz.pub
    $u =~ s/(\@[^.]+)?\.pub$//;    # baz.pub, baz@home.pub -> baz
    return $u;
}

# Extract the @selector part from the full path of the pubkey file
# (relative to keydir/ and including the '.pub' extension).
# If there is no @selector part, the empty string is returned.
# We also correctly extract the selector part from pending keys.
sub get_selector {
    my ($u) = @_; # filename of pubkey relative to keydir/.
    $u =~ s(.*/)();                # foo/bar/baz.pub -> baz.pub
    $u =~ s(\.pub$)();             # baz@home.pub -> baz@home
    return $1 if $u =~ m/.\@($required_self_pattern)$/; # baz@home -> home
    my ($zop, $zfp, $zselector, $zuser) = get_pending($u);
    # If $u was not a pending key, then $zselector is the empty string.
    return $zselector;
}

# Extract fingerprint, operation, selector, and true userid from a
# pending userid.
sub get_pending {
    my ($gl_user) = @_;
    return ($1, $2, $3, $4)
       if ($gl_user=~/^zzz-(...)-([0-9a-f]{32})-($required_self_pattern)-(.*)/);
    return ('', '', '', $gl_user)
}

# multiple / and are simplified to one / and the path is made relative
sub sanitize_pubkeypath {
    my ($pubkeypath) = @_;
    $pubkeypath =~ s|//|/|g; # normalize path
    $pubkeypath =~ s,\./,,g; # remove './' from path
    return './'.$pubkeypath; # Don't allow absolute paths.
}

# This function is only relavant for guest key managers.
# It returns true if the pattern is OK and false otherwise.
sub required_guest_keyid {
    local ($_) = @_;
    /$required_guest_pattern/ and ! /$forbidden_guest_pattern/;
}

# The function takes a $keyid as input and returns the keyid with the
# initial @ stripped if everything is fine. It aborts with an error if
# selfkey management is not enabled or the function is called for a
# non-self-key-manager.
# If the required selfkey pattern is not matched, it returns an empty string.
# Thus the function can be used to check whether a given keyid is a
# proper selfkeyid.
sub selfselector {
    my ($keyid) = @_;
    return '' unless $keyid =~ m(^\@($required_self_pattern)$);
    $keyid = $1;
    die "selfkey management is not enabled\n" unless $selfkey_management;
    die "You are not a selfkey manager.\n" if ! in_group('self-key-managers');
    return $keyid;
}

# Return the number of characters reserved for the userid field.
sub userid_width {
    my ($paths) = @_;
    my (%pkpaths) = %{$paths};
    my (@userid_lengths) = sort {$a <=> $b} (map {length($_)} keys %pkpaths);
    @userid_lengths ? $userid_lengths[-1] : 0;
}

# List the keys given by a reference to a hash.
# The regular expression $re is used to remove the initial part of the
# keyid and replace it by what is matched inside the parentheses.
# $format and $width are used for pretty printing
sub list_keys {
    my ($paths, $tokeyid, $format, $width) = @_;
    my (%pkpaths) = %{$paths};
    for my $userid (sort keys %pkpaths) {
        for my $pubkeypath (sort @{$pkpaths{$userid}}) {
            my $fp = $fingerprints{$pubkeypath};
            my $userid = $userids{$fp};
            my $keyid = &{$tokeyid}($pubkeypath);
            printf $format,$fp,$userid,$width+1-length($userid),"",$keyid
                if ($super_key_manager
                    || required_guest_keyid($keyid)
                    || $keyid=~m(^\@));
        }
    }
}

# Turn a pubkeypath into a keyid for super-key-managers, guest-keys,
# and self-keys.
sub superkeyid {
    my ($keyid) = @_;
    $keyid =~ s(\.pub$)();
    $keyid =~ s(^\./)();
    return $keyid;
}

sub guestkeyid {
    my ($keyid) = @_;
    $keyid =~ s(\.pub$)();
    $keyid =~ s(^.*/)();
    return $keyid;
}

sub selfkeyid {
    my ($keyid) = @_;
    $keyid =~ s(\.pub$)();
    $keyid =~ s(^.*/)();
    my ($zop, $zfp, $zselector, $zuser) = get_pending($keyid);
    return "\@$zselector (pending $zop)" if $zop;
    $keyid =~ s(.*@)(@);
    return $keyid;
}

###################################################################

# List public keys managed by the respective user.
# The fingerprints, userids and keyids are printed.
# keyids are shown in a form that can be used for add and del
# subcommands. 
sub list_pubkeys {
    print "Hello $gl_user, you manage the following keys:\n";
    my $format = "%-47s %s%*s%s\n";
    my $width = 0;
    if ($super_key_manager) {
        $width = userid_width(\%pubkeypaths);
        $width = 6 if $width < 6; # length("userid")==6
        printf $format, "fingerprint", "userid", ($width-5), "", "keyid";
        list_keys(\%pubkeypaths, , \&superkeyid, $format, $width);
    } else {
        my $widths = $selfkey_management?userid_width(\%self_pubkeypaths):0;
        my $widthg = userid_width(\%guest_pubkeypaths);
        $width = $widths > $widthg ? $widths : $widthg; # maximum width
        return unless $width; # there are no keys
        $width = 6 if $width < 6; # length("userid")==6
        printf $format, "fingerprint", "userid", ($width-5), "", "keyid";
        list_keys(\%self_pubkeypaths, \&selfkeyid, $format, $width)
            if $selfkey_management;
        list_keys(\%guest_pubkeypaths, \&guestkeyid,  $format, $width);
    }
}


###################################################################

# Add a public key for the user $gl_user.
# $pubkeypath is the place where the new key will be stored.
# If the file or its fingerprint already exists, the operation is
# rejected.
sub add_pubkey {
    my ( $gl_user, $pubkeypath, $keymaterial ) = @_;
    if(! $keymaterial) {
        print STDERR "Please supply the new key on STDIN.\n";
        print STDERR "Try something like this:\n";
        print STDERR "cat FOO.pub | ssh GIT\@GITOLITESERVER ukm add KEYID\n";
        die "missing public key data\n";
    }
    # clean pubkeypath a bit
    $pubkeypath = sanitize_pubkeypath($pubkeypath);
    # Check that there is not yet something there already.
    die "cannot override existing key\n" if $fingerprints{$pubkeypath};

    my $userid = get_userid($pubkeypath);
    # Super key managers shouldn't be able to add a that leads to
    # either an empty userid or to a userid that starts with @.
    #
    # To avoid confusion, all keyids for super key managers must be in
    # a full path format. Having a public key of the form
    # gitolite-admin/keydir/@foo.pub might be confusing and might lead
    # to other problems elsewhere.
    die "cannot add key that starts with \@\n" if (!$userid) || $userid=~/^@/;

    cd_temp_clone();
    _chdir("keydir");
    $pubkeypath =~ m((.*)/); # get the directory part
    _mkdir($1);
    _print($pubkeypath, $keymaterial);
    my $fp = fingerprint($pubkeypath);

    # Maybe we are adding a selfkey.
    my ($zop, $zfp, $zselector, $zuser) = get_pending($userid);
    my $user = $zop ? "$zuser\@$zselector" : $userid;
    $userid = $zuser;
    # Check that there isn't a key with the same fingerprint under a
    # different userid.
    if (defined $userids{$fp}) {
        if ($userid ne $userids{$fp}) {
            print STDERR "Found  $fp $userids{$fp}\n" if $super_key_manager;
            print STDERR "Same key is already available under another userid.\n";
            die "cannot add key\n";
        } elsif ($zop) {
            # Because of the way a key is confirmed with ukm, it is
            # impossible to confirm the initial key of the user as a
            # new selfkey. (It will lead to the function list_pubkeys
            # instead of pending_user_add, because the gl_user will
            # not be that of a pending user.) To avoid confusion, we,
            # therefore, forbid to add the user's initial key
            # altogether.
            # In fact, we here also forbid to add any key for that
            # user that is already in the system.
            die "You cannot add a key that already belongs to you.\n";
        }
    } else {# this fingerprint does not yet exist
        my @paths = @{$pubkeypaths{$userid}} if defined $pubkeypaths{$userid};
        if (@paths) {# there are already keys for $userid
            if (grep {$pubkeypath eq $_} @paths) {
                print STDERR "The keyid is already present. Nothing changed.\n";
            } elsif ($super_key_manager) {
                # It's OK to add new selfkeys, but here we are in the case
                # of adding multiple keys for guests. That is forbidden.
                print STDERR "Adding new public key for $userid.\n";
            } elsif ($pubkeypath =~ m(^\./$guestkeys_dir/)) {
                # Arriving here means we are about to add a *new*
                # guest key, because the fingerprint is not yet
                # existing. This would be for an already existing
                # userid (added by another guest key manager). Since
                # that effectively means to (silently) add an
                # additional key for an existing user, it must be
                # forbidden.
                die "cannot add another public key for an existing user\n";
            }
        }
    }
    exit if (`git status -s` eq ''); # OK to add identical keys twice
    hushed_git( "add", "." ) and die "git add failed\n";
    hushed_git( "commit", "-m", "'ukm add $gl_user $userid\n\n$fp'" )
        and die "git commit failed\n";
    system("gitolite push >/dev/null 2>/dev/null") and die "git push failed\n";
}

# Guest key managers should not be allowed to add directories or
# multiple keys via the @domain mechanism, since this might allow
# another guest key manager to give an attacker access to another
# user's repositories.
#
# Example: Alice adds bob.pub for bob@example.org. David adds eve.pub
# (where only Eve but not Bob has the private key) under the keyid
# bob@example.org@foo. This basically gives Eve the same rights as
# Bob.
sub add_guest {
    my ( $gl_user, $keyid, $keymaterial ) = @_;
    die "keyid not allowed: '$keyid'\n"
        if $keyid =~ m(@.*@) or $keyid =~ m(/) or !required_guest_keyid($keyid);
    add_pubkey($gl_user, "$guestkeys_dir/$gl_user/$keyid.pub", $keymaterial);
}

# Add a new selfkey for user $gl_user.
sub add_self {
    my ( $gl_user, $keyid, $keymaterial ) = @_;
    my $selector = "";
    $selector = selfselector($keyid); # might return empty string
    die "keyid not allowed: $keyid\n" unless $selector;

    # Check that the new selector is not already in use even not in a
    # pending state.
    die "keyid already in use: $keyid\n"
        if grep {selfkeyid($_)=~/^\@$selector( .*)?$/} @{$self_pubkeypaths{$gl_user}};
    # generate new pubkey create fingerprint
    system("ssh-keygen -N '' -q -f \"$TEMPDIR/session\" -C $gl_user");
    my $sessionfp = fingerprint("$TEMPDIR/session.pub");
    $sessionfp =~ s/://g;
    my $user = "zzz-add-$sessionfp-$selector-$gl_user";
    add_pubkey($gl_user, "$selfkeys_dir/$gl_user/$user.pub", $keymaterial);
    print `cat "$TEMPDIR/session.pub"`;
}

###################################################################


# Delete a key of user $gl_user.
sub del_pubkey {
    my ($gl_user, $pubkeypath) = @_;
    $pubkeypath = sanitize_pubkeypath($pubkeypath);
    my $fp = $fingerprints{$pubkeypath};
    die "key not found\n" unless $fp;
    cd_temp_clone();
    chdir("keydir");
    hushed_git( "rm", "$pubkeypath" ) and die "git rm failed\n";
    my $userid = get_userid($pubkeypath);
    hushed_git( "commit", "-m", "'ukm del $gl_user $userid\n\n$fp'" )
        and die "git commit failed\n";
    system("gitolite push >/dev/null 2>/dev/null") and die "git push failed\n";
}

# $gl_user is a super key manager. This function aborts if the
# superkey manager tries to remove his last key.
sub del_super {
    my ($gl_user, $pubkeypath) = @_;
    $pubkeypath = sanitize_pubkeypath($pubkeypath);
    die "You are not managing the key $keyid.\n"
        unless grep {$_ eq $pubkeypath} @all_pubkeypaths;
    my $userid = get_userid($pubkeypath);
    if ($gl_user eq $userid) {
        my @paths = @{$pubkeypaths{$userid}};
        die "You cannot delete your last key.\n"
            if scalar(grep {$userid eq get_userid($_)} @paths)<2;
    }
    del_pubkey($gl_user, $pubkeypath);
}

sub del_guest {
    my ($gl_user, $keyid) = @_;
    my $pubkeypath = sanitize_pubkeypath("$guestkeys_dir/$gl_user/$keyid.pub");
    my $userid = get_userid($pubkeypath);
    # Check whether $gl_user actually manages $keyid.
    my @paths = ();
    @paths = @{$guest_pubkeypaths{$userid}}
        if defined $guest_pubkeypaths{$userid};
    die "You are not managing the key $keyid.\n"
        unless grep {$_ eq $pubkeypath} @paths;
    del_pubkey($gl_user, $pubkeypath);
}

# Delete a selfkey of $gl_user. The first delete is a preparation of
# the deletion and only a second call will actually delete the key. If
# the second call is done with the key that is scheduled for deletion,
# it is basically undoing the previous del call. This last case is
# handled in function pending_user_del.
sub del_self {
    my ($gl_user, $keyid) = @_;
    my $selector = selfselector($keyid); # might return empty string
    die "keyid not allowed: '$keyid'\n" unless $selector;

    # Does $gl_user actually manage that keyid?
    # All (non-pending) selfkeys have an @selector part in their pubkeypath.
    my @paths = @{$self_pubkeypaths{$gl_user}};
    die "You are not managing the key $keyid.\n"
        unless grep {$selector eq get_selector($_)} @paths;

    cd_temp_clone();
    _chdir("keydir");
    my $fp = '';
    # Is it the first or the second del call? It's the second call, if
    # there is a scheduled-for-deletion or scheduled-for-addition
    # selfkey which has the given keyid as a selector part.
    @paths = grep {
        my ($zop, $zfp, $zselector, $zuser) = get_pending(get_userid($_));
        $zselector eq $selector
    } @paths;
    if (@paths) {# start actual deletion of the key (second call)
        my $pubkeypath = $paths[0];
        $fp = fingerprint($pubkeypath);
        my ($zop, $zf, $zs, $zu) = get_pending(get_userid($pubkeypath));
        $zop = $zop eq 'add' ? 'undo-add' : 'confirm-del';
        hushed_git("rm", "$pubkeypath") and die "git rm failed\n";
        hushed_git("commit", "-m", "'ukm $zop $gl_user\@$selector\n\n$fp'")
            and die "git commit failed\n";
        system("gitolite push >/dev/null 2>/dev/null")
            and die "git push failed\n";
        print STDERR "pending keyid deleted: \@$selector\n";
        return;
    }
    my $oldpubkeypath = "$selfkeys_dir/$gl_user/$gl_user\@$selector.pub";
    # generate new pubkey and create fingerprint to get a random number
    system("ssh-keygen -N '' -q -f \"$TEMPDIR/session\" -C $gl_user");
    my $sessionfp = fingerprint("$TEMPDIR/session.pub");
    $sessionfp =~ s/://g;
    my $user = "zzz-del-$sessionfp-$selector-$gl_user";
    my $newpubkeypath = "$selfkeys_dir/$gl_user/$user.pub";

    # A key for gitolite access that is in authorized_keys and not
    # existing in the expected place under keydir/ should actually not
    # happen, but one never knows.
    die "key not available\n" unless -r $oldpubkeypath;

    # For some strange reason the target key already exists.
    die "cannot override existing key\n" if -e $newpubkeypath;

    $fp = fingerprint($oldpubkeypath);
    print STDERR "prepare deletion of key \@$selector\n";
    hushed_git("mv", "$oldpubkeypath", "$newpubkeypath")
        and die "git mv failed\n";
    hushed_git("commit", "-m", "'ukm prepare-del $gl_user\@$selector\n\n$fp'")
        and die "git commit failed\n";
    system("gitolite push >/dev/null 2>/dev/null")
        and die "git push failed\n";
}

###################################################################
# Adding a selfkey should be done as follows.
#
#   cat newkey.pub | ssh git@host ukm add @selector > session
#   cat session | ssh -i newkey git@host ukm
#
# The provided random data will come from a newly generated ssh key
# whose fingerprint will be stored in $gl_user. So we compute the
# fingerprint of the data that is given to us. If it doesn't match the
# fingerprint, then something went wrong and the confirm operation is
# forbidden, in fact, the pending key will be removed from the system.
sub pending_user_add {
    my ($gl_user, $zfp, $zselector, $zuser) = @_;
    my $oldpubkeypath = "$selfkeys_dir/$zuser/$gl_user.pub";
    my $newpubkeypath = "$selfkeys_dir/$zuser/$zuser\@$zselector.pub";

    # A key for gitolite access that is in authorized_keys and not
    # existing in the expected place under keydir/ should actually not
    # happen, but one never knows.
    die "key not available\n" unless -r $oldpubkeypath;

    my $keymaterial = safe_stdin();
    # If there is no keymaterial (which corresponds to a session key
    # for the confirm-add operation), logging in to this key, removes
    # it from the system.
    my $session_key_not_provided = '';
    if (!$keymaterial) {
        $session_key_not_provided = "missing session key";
    } else {
        _print("$TEMPDIR/session.pub", $keymaterial);
        my $sessionfp = fingerprint("$TEMPDIR/session.pub");
        $sessionfp =~ s/://g;
        $session_key_not_provided = "session key not accepted"
            unless ($zfp eq $sessionfp)
    }
    my $fp = fingerprint($oldpubkeypath);
    if ($session_key_not_provided) {
        print STDERR "$session_key_not_provided\n";
        print STDERR "pending keyid deleted: \@$zselector\n";
        hushed_git("rm", "$oldpubkeypath") and die "git rm failed\n";
        hushed_git("commit", "-m", "'ukm del $zuser\@$zselector\n\n$fp'")
            and die "git commit failed\n";
        system("gitolite push >/dev/null 2>/dev/null")
            and die "git push failed\n";
        return;
    }

    # For some strange reason the target key already exists.
    die "cannot override existing key\n" if -e $newpubkeypath;

    print STDERR "pending keyid added: \@$zselector\n";
    hushed_git("mv", "$oldpubkeypath", "$newpubkeypath")
        and die "git mv failed\n";
    hushed_git("commit", "-m", "'ukm confirm-add $zuser\@$zselector\n\n$fp'")
        and die "git commit failed\n";
    system("gitolite push >/dev/null 2>/dev/null")
        and die "git push failed\n";
}

# To delete a key, one must first bring the key into a pending state
# and then truely delete it with another key. In case, the login
# happens with the pending key (implemented below), it means that the
# delete operation has to be undone.
sub pending_user_del {
    my ($gl_user, $zfp, $zselector, $zuser) = @_;
    my $oldpubkeypath = "$selfkeys_dir/$zuser/$gl_user.pub";
    my $newpubkeypath = "$selfkeys_dir/$zuser/$zuser\@$zselector.pub";
    print STDERR "undo pending deletion of keyid \@$zselector\n";
    # A key for gitolite access that is in authorized_keys and not
    # existing in the expected place under keydir/ should actually not
    # happen, but one never knows.
    die "key not available\n" unless -r $oldpubkeypath;
    # For some strange reason the target key already exists.
    die "cannot override existing key\n" if -e $newpubkeypath;
    my $fp = fingerprint($oldpubkeypath);
    hushed_git("mv", "$oldpubkeypath", "$newpubkeypath")
        and die "git mv failed\n";
    hushed_git("commit", "-m", "'ukm undo-del $zuser\@$zselector\n\n$fp'")
        and die "git commit failed\n";
}

# A user whose key is in pending state cannot do much. In fact,
# logging in as such a user simply takes back the "bringing into
# pending state", i.e. a key scheduled for adding is remove and a key
# scheduled for deletion is brought back into its properly added state.
sub pending_user {
    my ($gl_user, $zop, $zfp, $zselector, $zuser) = @_;
    cd_temp_clone();
    _chdir("keydir");
    if ($zop eq 'add') {
        pending_user_add($gl_user, $zfp, $zselector, $zuser);
    } elsif ($zop eq 'del') {
        pending_user_del($gl_user, $zfp, $zselector, $zuser);
    } else {
        die "unknown operation\n";
    }
    system("gitolite push >/dev/null 2>/dev/null")
        and die "git push failed\n";
}


================================================
FILE: contrib/hooks/repo-specific/save-push-signatures
================================================
#!/bin/sh

# ----------------------------------------------------------------------
# post-receive hook to adopt push certs into 'refs/push-certs'

# Collects the cert blob on push and saves it, then, if a certain number of
# signed pushes have been seen, processes all the "saved" blobs in one go,
# adding them to the special ref 'refs/push-certs'.  This is done in a way
# that allows searching for all the certs pertaining to one specific branch
# (thanks to Junio Hamano for this idea plus general brainstorming).

# The "collection" happens only if $GIT_PUSH_CERT_NONCE_STATUS = OK; again,
# thanks to Junio for pointing this out; see [1]
#
# [1]: https://groups.google.com/forum/#!topic/gitolite/7cSrU6JorEY

# WARNINGS:
#   Does not check that GIT_PUSH_CERT_STATUS = "G".  If you want to check that
#   and FAIL the push, you'll have to write a simple pre-receive hook
#   (post-receive is not the place for that; see 'man githooks').
#
#   Gitolite users: failing the hook cannot be done as a VREF because git does
#   not set those environment variables in the update hook.  You'll have to
#   write a trivial pre-receive hook and add that in.

# Relevant gitolite doc links:
#   repo-specific environment variables
#       http://gitolite.com/gitolite/dev-notes.html#appendix-1-repo-specific-environment-variables
#   repo-specific hooks
#       http://gitolite.com/gitolite/non-core.html#repo-specific-hooks
#       http://gitolite.com/gitolite/cookbook.html#v36-variation-repo-specific-hooks

# Environment:
#   GIT_PUSH_CERT_NONCE_STATUS should be "OK" (as mentioned above)
#
#   GL_OPTIONS_GPC_PENDING (optional; defaults to 1).  This is the number of
#   git push certs that should be waiting in order to trigger the post
#   processing.  You can set it within gitolite like so:
#
#       repo foo bar    # or maybe just 'repo @all'
#           option ENV.GPC_PENDING = 5

# Setup:
#   Set up this code as a post-receive hook for whatever repos you need to.
#   Then arrange to have the environment variable GL_OPTION_GPC_PENDING set to
#   some number, as shown above.  (This is only required if you need it to be
#   greater than 1.)  It could of course be different for different repos.
#   Also see "Invocation" section below.

# Invocation:
#   Normally via git (see 'man githooks'), once it is setup as a post-receive
#   hook.
#
#   However, if you set the "pending" limit high, and want to periodically
#   "clean up" pending certs without necessarily waiting for the counter to
#   trip, do the following (untested):
#
#       RB=$(gitolite query-rc GL_REPO_BASE)
#       for r in $(gitolite list-phy-repos)
#       do
#           cd $RB/$repo.git
#           unset GL_OPTIONS_GPC_PENDING    # if it is set higher up
#           hooks/post-receive post_process
#       done
#
#   That will take care of it.

# Using without gitolite:
#   Just set GL_OPTIONS_GPC_PENDING within the script (maybe read it from git
#   config).  Everything else is independent of gitolite.

# ----------------------------------------------------------------------
# make it work on BSD also (but NOT YET TESTED on FreeBSD!)
uname_s=`uname -s`
if [ "$uname_s" = "Linux" ]
then
    _lock() { flock "$@"; }
else
    _lock() { lockf -k "$@"; }
    # I'm assuming other BSDs also have this; I only have FreeBSD.
fi

# ----------------------------------------------------------------------
# standard stuff
die() { echo "$@" >&2; exit 1; }
warn() { echo "$@" >&2; }

# ----------------------------------------------------------------------
# if there are no arguments, we're running as a "post-receive" hook
if [ -z "$1" ]
then
    # ignore if it may be a replay attack
    [ "$GIT_PUSH_CERT_NONCE_STATUS" = "OK" ] || exit 1
    # I don't think "exit 1" does anything in a post-receive anyway, so that's
    # just a symbolic gesture!

    # note the lock file used
    _lock .gpc.lock $0 cat_blob

    # if you want to initiate the post-processing ONLY from outside (for
    # example via cron), comment out the next line.
    exec $0 post_process
fi

# ----------------------------------------------------------------------
# the 'post_process' part; see "Invocation" section in the doc at the top
if [ "$1" = "post_process" ]
then
    # this is the same lock file as above
    _lock .gpc.lock $0 count_and_rotate $$

    [ -d git-push-certs.$$ ] || exit 0

    # but this is a different one
    _lock .gpc.ref.lock $0 update_ref $$

    exit 0
fi

# ----------------------------------------------------------------------
# other values for "$1" are internal use only

if [ "$1" = "cat_blob" ]
then
    mkdir -p git-push-certs
    git cat-file blob $GIT_PUSH_CERT > git-push-certs/$GIT_PUSH_CERT
    echo $GIT_PUSH_CERT >> git-push-certs/.blob.list
fi

if [ "$1" = "count_and_rotate" ]
then
    count=$(ls git-push-certs | wc -l)
    if test $count -ge ${GL_OPTIONS_GPC_PENDING:-1}
    then
        # rotate the directory
        mv git-push-certs git-push-certs.$2
    fi
fi

if [ "$1" = "update_ref" ]
then
    # use a different index file for all this
    GIT_INDEX_FILE=push_certs_index; export GIT_INDEX_FILE

    # prepare the special ref to receive commits
    # historically this hook put the certs in a ref named refs/push-certs
    # however, git does *NOT* replicate single-level refs
    # trying to push them explicitly causes this error:
    # remote: error: refusing to create funny ref 'refs/push-certs' remotely
    # https://lore.kernel.org/git/robbat2-20211115T063838-612792475Z@orbis-terrarum.net/
    #
    # As a good-enough solution, use the namespace of meta/ for the refs.
    # This is already used in other systems:
    # - kernel.org refs/meta/cgit
    # - gerrit refs/meta/config
    # - GitBlit reflog: refs/meta/gitblit https://www.gitblit.com/administration.html#H12
    # - cc-utils refs/meta/ci
    # - JGit refs/meta/push-certs https://www.ibm.com/docs/en/radfws/9.6.1?topic=SSRTLW_9.6.1/org.eclipse.egit.doc/help/JGit/New_and_Noteworthy/4.1/4.1.htm
    #
    # To migrate from old to new, for each repo:
    # git update-ref refs/meta/push-certs refs/push-certs
    PUSH_CERTS_EXTRA_REFS='' PUSH_CERTS='' # These vars will be populated after checks.
    # others vars are temp
    _OLD_PUSH_CERTS=refs/push-certs
    _NEW_PUSH_CERTS=refs/meta/push-certs
    _OLD_PUSH_CERTS_EXISTS=0
    _NEW_PUSH_CERTS_EXISTS=0
    git show-ref --verify --quiet -- "$_OLD_PUSH_CERTS" && _OLD_PUSH_CERTS_EXISTS=1
    git show-ref --verify --quiet -- "$_NEW_PUSH_CERTS" && _NEW_PUSH_CERTS_EXISTS=1
    case "${_OLD_PUSH_CERTS_EXISTS}${_NEW_PUSH_CERTS_EXISTS}" in
        # neither or new only:
        # let's push to the NEW name only
        '00'|'01') PUSH_CERTS=$_NEW_PUSH_CERTS ;;
        # old-only: stick to the same, the migration is opt-in
        '10') PUSH_CERTS=$_OLD_PUSH_CERTS ;;
        # Both: Push to the old name, duplicate to the new name
        '11') PUSH_CERTS=$_OLD_PUSH_CERTS PUSH_CERTS_EXTRA_REFS=$_NEW_PUSH_CERTS ;;
    esac
    # cleanup vars
    unset _OLD_PUSH_CERTS_EXISTS _NEW_PUSH_CERTS_EXISTS _OLD_PUSH_CERTS _NEW_PUSH_CERTS

    if git rev-parse -q --verify $PUSH_CERTS >/dev/null
    then
        git read-tree $PUSH_CERTS
    else
        git read-tree --empty
        T=$(git write-tree)
        C=$(echo 'start' | git commit-tree $T)
        for _ref in $PUSH_CERTS $PUSH_CERTS_EXTRA_REFS ; do
            git update-ref "${_ref}" "${C}"
        done
    fi

    # for each cert blob...
    for b in `cat git-push-certs.$2/.blob.list`
    do
        cf=git-push-certs.$2/$b

        # it's highly unlikely that the blob got GC-ed already but write it
        # back anyway, just in case
        B=$(git hash-object -w $cf)

        # bit of a sanity check
        [ "$B" = "$b" ] || warn "this should not happen: $B is not equal to $b"

        # for each ref described within the cert, update the index
        for ref in `cat $cf | egrep '^[a-f0-9]+ [a-f0-9]+ refs/' | cut -f3 -d' '`
        do
            git update-index --add --cacheinfo 100644,$b,$ref
            # we're using the ref name as a "fake" filename, so people can,
            # for example, 'git log refs/push-certs -- refs/heads/master', to
            # see all the push certs pertaining to the master branch.  This
            # idea came from Junio Hamano, the git maintainer (I certainly
            # don't deal with git plumbing enough to have thought of it!)
        done

        T=$(git write-tree)
        C=$( git commit-tree -p $PUSH_CERTS $T < $cf )
        for _ref in $PUSH_CERTS $PUSH_CERTS_EXTRA_REFS ; do
            git update-ref "${_ref}" "${C}"
        done

        rm -f $cf
    done
    rm -f git-push-certs.$2/.blob.list
    rmdir git-push-certs.$2
fi


================================================
FILE: contrib/lib/Apache/gitolite.conf
================================================
# Apache Gitolite smart-http install Active Directory Authentication

# Author: Jonathan Gray

# It is assumed you already have mod_ssl, mod_ldap, & mod_authnz configured for apache
# It is also assumed you are disabling http on port 80 and requiring the use of https on port 443

# Boiler plate configuration from the smart-http deployment documentation script
# Adjust paths if you use something other than the default
SetEnv GIT_PROJECT_ROOT /var/www/gitolite-home/repositories
ScriptAlias /git/ /var/www/gitolite-home/gitolite-source/src/gitolite-shell/
ScriptAlias /gitmob/ /var/www/gitolite-home/gitolite-source/src/gitolite-shell/
SetEnv GITOLITE_HTTP_HOME /var/www/gitolite-home
# WARNING: do not add a trailing slash to the value of GITOLITE_HTTP_HOME
SetEnv GIT_HTTP_EXPORT_ALL
 
# Setup LDAP trusted root certificate from your domain
LDAPTrustedGlobalCert CA_BASE64 /etc/httpd/conf.d/domain.ca.cer

# In case you havn't setup proper SSL certificates in ssl.conf, go ahead and do it here to save headache later with git
SSLCertificateFile /etc/httpd/conf.d/gitolite.server.crt
SSLCertificateKeyFile /etc/httpd/conf.d/gitolite.server.key
SSLCertificateChainFile /etc/httpd/conf.d/DigiCertCA.crt
 
<Location /git>
        Order deny,allow
	# In case you want to restrict access to a given ip/subnet
        #Allow from my.ip.range/cidr
        #Deny from All
        AuthType Basic
        AuthName "Git"
        AuthBasicProvider ldap
        AuthUserFile /dev/null
        AuthzLDAPAuthoritative on
        AuthLDAPURL ldaps://AD.DC1.local:3269 AD.DC2.local:3269 AD.DC3.local:3269/?sAMAccountName?sub
        AuthLDAPBindDN git@domain.local
        AuthLDAPBindPassword super.secret.password
        AuthLDAPGroupAttributeIsDN on
 
	# You must use one of the two following approaches to handle authentication via active directory
	
        # Require membership in the gitolite users group in AD
        # The ldap-filter option is used to handle nested groups on the AD server rather than multiple calls to traverse from apache
        # Require ldap-filter memberof:1.2.840.113556.1.4.1941:=cn=Gitolite Users,ou=Security Groups,dc=domain,dc=local

	# Alternatively, require a valid user account only since you're going to control authorization in gitolite anyway
	Require valid-user
</Location>


================================================
FILE: contrib/lib/Gitolite/Triggers/RedmineUserAlias.pm
================================================
package Gitolite::Triggers::RedmineUserAlias;

use Gitolite::Rc;
use Gitolite::Common;
use Gitolite::Conf::Load;

use strict;
use warnings;

# aliasing a redmine username to a more user-friendly one
# ----------------------------------------------------------------------

=for usage

Why:

    Redmine creates users like "redmine_alice_123"; we want the users to just
    see "alice" instead of that.

Assumption:

*   Redmine does not allow duplicates in the middle bit; i.e., you can't
    create redmine_alice_123 and redmine_alice_456 also.

How:

*   add this code as lib/Gitolite/Triggers/RedmineUserAlias.pm to your
    site-local code directory; see this link for how:

        http://gitolite.com/gitolite/non-core.html#locations

*   add the following to the rc file, just before the ENABLE section (don't
    forget the trailing comma):

        INPUT   =>  [ 'RedmineUserAlias::input' ],

Notes:

*   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.

*   not tested with mirroring.

Quote:

*   "All that for what is effectively one line of code.  I need a life".

=cut

sub input {
    $ARGV[0] or _die "no username???";
    $ARGV[0] =~ s/^redmine_(\S+)_\d+$/$1/;
}

1;


================================================
FILE: contrib/t/ukm.t
================================================
#!/usr/bin/perl

# Call like this:
# TSH_VERBOSE=1 TSH_ERREXIT=1 HARNESS_ACTIVE=1 GITOLITE_TEST=y prove t/ukm.t

use strict;
use warnings;

# this is hardcoded; change it if needed
use lib "src/lib";
use Gitolite::Common;
use Gitolite::Test;

# basic tests using ssh
# ----------------------------------------------------------------------

my $bd = `gitolite query-rc -n GL_BINDIR`;
my $h  = $ENV{HOME};
my $ab = `gitolite query-rc -n GL_ADMIN_BASE`;
my $pd = "$bd/../t/keys"; # source for pubkeys
umask 0077;

_mkdir( "$h/.ssh", 0700 ) if not -d "$h/.ssh";

try "plan 204";


# Reset everything.
# Only admin and u1, u2, and u3 keys are available initially
# Keys u4, u5, and u6 are used as guests later.
# For easy access, we put the keys into ~/.ssh/, though.
try "
    rm -f $h/.ssh/authorized_keys; ok or die 1
    cp $pd/u[1-6]* $h/.ssh; ok or die 2
    cp $pd/admin*  $h/.ssh; ok or die 3
    cp $pd/config  $h/.ssh; ok or die 4
        cat $h/.ssh/config
        perl s/%USER/$ENV{USER}/
        put $h/.ssh/config
    mkdir             $ab/keydir; ok or die 5
    cp $pd/u[1-3].pub $ab/keydir; ok or die 6
    cp $pd/admin.pub  $ab/keydir; ok or die 7
";

# Put the keys into ~/.ssh/authorized_keys
system("gitolite ../triggers/post-compile/ssh-authkeys");

# enable user key management in a simple form.
# Guest key managers can add keyids looking like email addresses, but
# cannot add emails containing example.com or hemmecke.org.
system("sed -i \"s/.*ENABLE =>.*/'UKM_CONFIG'=>{'FORBIDDEN_GUEST_PATTERN'=>'example.com|hemmecke.org'}, ENABLE => ['ukm',/\" $h/.gitolite.rc");

# super-key-managers can add/del any key
# super-key-managers should in fact agree with people having write
# access to gitolite-admin repo.
# guest-key-managers can add/del guest keys
confreset; confadd '
    @guest-key-managers = u2 u3
    @creators = u2 u3
    repo pub/CREATOR/..*
        C   =   @creators
        RW+ =   CREATOR
        RW  =   WRITERS
        R   =   READERS
';

# Populate the gitolite-admin/keydir in the same way as it was used for
# the initialization of .ssh/authorized_keys above.
try "
    mkdir             keydir; ok or die 8
    cp $pd/u[1-3].pub keydir; ok or die 9;
    cp $pd/admin.pub  keydir; ok or die 10;
    git add conf keydir; ok
    git commit -m ukm; ok; /master.* ukm/
";

# Activate new config data.
try "PUSH admin; ok; gsh; /master -> master/; !/FATAL/" or die text();

# Check whether the above setup yields the expected behavior for ukm.
# The admin is super-key-manager, thus can manage every key.
try "
    ssh admin ukm; ok; /Hello admin, you manage the following keys:/
                       / admin +admin/
                       / u1 +u1/
                       / u2 +u2/
                       / u3 +u3/
";

# u1 isn't a key manager, so shouldn't be above to manage keys.
try "ssh u1 ukm; !ok; /FATAL: You are not a key manager./";

# u2 and u3 are guest key managers, but don't yet manage any key.
try "ssh u2 ukm; ok"; cmp "Hello u2, you manage the following keys:\n\n\n";
try "ssh u3 ukm; ok"; cmp "Hello u3, you manage the following keys:\n\n\n";


###################################################################
# Unknows subkommands abort ukm.
try "ssh u2 ukm fake; !ok; /FATAL: unknown ukm subcommand: fake/";


###################################################################
# Addition of keys.

# If no data is provided on stdin, we don't block, but rather timeout
# after one second and abort the program.
try "ssh u2 ukm add u4\@example.org; !ok; /FATAL: missing public key data/";

# If no keyid is given, we cannot add a key.
try "ssh u2 ukm add; !ok; /FATAL: keyid required/";

try "
    DEF ADD = cat $pd/%1.pub|ssh %2 ukm add %3
    DEF ADDOK = ADD %1 %2 %3; ok
    DEF ADDNOK = ADD %1 %2 %3; !ok
    DEF FP = ADDNOK u4 u2 %1
    DEF FORBIDDEN_PATTERN = FP %1; /FATAL: keyid not allowed:/
";

# Neither a guest key manager nor a super key manager can add keys that have
# double dot in their keyid. This is hardcoded to forbid paths with .. in it.
try "
    ADDNOK u4 u2    u4\@hemmecke..org; /Not allowed to use '..' in keyid./
    ADDNOK u4 admin u4\@hemmecke..org; /Not allowed to use '..' in keyid./
    ADDNOK u4 admin ./../.myshrc;      /Not allowed to use '..' in keyid./
";

# guest-key-managers can only add keys that look like emails.
try "
    FORBIDDEN_PATTERN u4
    FORBIDDEN_PATTERN u4\@example
    FORBIDDEN_PATTERN u4\@foo\@example.org

    # No support for 'old style' multiple keys.
    FORBIDDEN_PATTERN u4\@example.org\@foo

    # No path delimiter in keyid
    FORBIDDEN_PATTERN foo/u4\@example.org

    # Certain specific domains listed in FORBIDDEN_GUEST_PATTERN are forbidden.
    # Note that also u4\@example-com would be rejected, because MYDOMAIN
    # contains a regular expression --> I don't care.
    FORBIDDEN_PATTERN u4\@example.com
    FORBIDDEN_PATTERN u4\@hemmecke.org
";

# Accept one guest key.
try "ADDOK u4 u2 u4\@example.org";
try "ssh u2 ukm; ok; /Hello u2, you manage the following keys:/
                     / u4\@example.org *u4\@example.org/";

# Various ways how a key must be rejected.
try "
    # Cannot add the same key again.
    ADDNOK u4 u2 u4\@example.org; /FATAL: cannot override existing key/

    # u2 can also not add u4.pub under another keyid
    ADDNOK u4 u2 u4\@example.net; /FATAL: cannot add key/
         /Same key is already available under another userid./

    # u2 can also not add another key under the same keyid.
    ADDNOK u5 u2 u4\@example.org; /FATAL: cannot override existing key/

    # Also u3 cannot not add another key under the same keyid.
    ADDNOK u5 u3 u4\@example.org
         /FATAL: cannot add another public key for an existing user/

    # And u3 cannot not add u4.pub under another keyid.
    ADDNOK u4 u3 u4\@example.net; /FATAL: cannot add key/
         /Same key is already available under another userid./

    # Not even the admin can add the same key u4 under a different userid.
    ADDNOK u4 admin u4\@example.net; /FATAL: cannot add key/
         /Same key is already available under another userid./
         /Found  .* u4\@example.org/

    # Super key managers cannot add keys that start with @.
    # We don't care about @ in the dirname, though.
    ADDNOK u4 admin foo/\@ex.net; /FATAL: cannot add key that starts with \@/
    ADDNOK u4 admin foo/\@ex;     /FATAL: cannot add key that starts with \@/
    ADDNOK u4 admin     \@ex.net; /FATAL: cannot add key that starts with \@/
    ADDNOK u4 admin     \@ex;     /FATAL: cannot add key that starts with \@/
";

# But u3 can add u4.pub under the same keyid.
try "ADDOK u4 u3 u4\@example.org";

try "ssh u3 ukm; ok; /Hello u3, you manage the following keys:/
                     / u4\@example.org *u4\@example.org/";

# The admin can add multiple keys for the same userid.
try "
    ADDOK u5 admin u4\@example.org
    ADDOK u5 admin u4\@example.org\@home
    ADDOK u5 admin laptop/u4\@example.org
    ADDOK u5 admin laptop/u4\@example.org\@home
";

# And admin can also do this for other guest key managers. Note,
# however, that the gitolite-admin must be told where the
# GUEST_DIRECTORY is. But he/she could find out by cloning the
# gitolite-admin repository and adding the same key directly.
try "
    ADDOK u5 admin zzz/guests/u2/u4\@example.org\@foo
    ADDOK u6 admin zzz/guests/u3/u6\@example.org
";

try "ssh admin ukm; ok"; cmp "Hello admin, you manage the following keys:
fingerprint                                     userid         keyid
a4:d1:11:1d:25:5c:55:9b:5f:91:37:0e:44:a5:a5:f2 admin          admin
00:2c:1f:dd:a3:76:5a:1e:c4:3c:01:15:65:19:a5:2e u1             u1
69:6f:b5:8a:f5:7b:d8:40:ce:94:09:a2:b8:95:79:5b u2             u2
26:4b:20:24:98:a4:e4:a5:b9:97:76:9a:15:92:27:2d u3             u3
78:cf:7e:2b:bf:18:58:54:23:cc:4b:3d:7e:f4:63:79 u4\@example.org laptop/u4\@example.org
78:cf:7e:2b:bf:18:58:54:23:cc:4b:3d:7e:f4:63:79 u4\@example.org laptop/u4\@example.org\@home
78:cf:7e:2b:bf:18:58:54:23:cc:4b:3d:7e:f4:63:79 u4\@example.org u4\@example.org
78:cf:7e:2b:bf:18:58:54:23:cc:4b:3d:7e:f4:63:79 u4\@example.org u4\@example.org\@home
8c:a6:c0:a5:71:85:0b:89:d3:08:97:22:ae:95:e1:bb u4\@example.org zzz/guests/u2/u4\@example.org
78:cf:7e:2b:bf:18:58:54:23:cc:4b:3d:7e:f4:63:79 u4\@example.org zzz/guests/u2/u4\@example.org\@foo
8c:a6:c0:a5:71:85:0b:89:d3:08:97:22:ae:95:e1:bb u4\@example.org zzz/guests/u3/u4\@example.org
fc:0f:eb:52:7a:d2:35:da:89:96:f5:15:0e:85:46:e7 u6\@example.org zzz/guests/u3/u6\@example.org
\n\n";

# Now, u2 has two keys in his directory, but u2 can manage only one of
# them, since the one added by the admin has two @ in it. Thus the key
# added by admin is invisible to u2.
try "ssh u2 ukm; ok"; cmp "Hello u2, you manage the following keys:
fingerprint                                     userid         keyid
8c:a6:c0:a5:71:85:0b:89:d3:08:97:22:ae:95:e1:bb u4\@example.org u4\@example.org
\n\n";

# Since admin added key u6@example.org to the directory of u2, u2 is
# also able to see it and, in fact, to manage it.
try "ssh u3 ukm; ok"; cmp "Hello u3, you manage the following keys:
fingerprint                                     userid         keyid
8c:a6:c0:a5:71:85:0b:89:d3:08:97:22:ae:95:e1:bb u4\@example.org u4\@example.org
fc:0f:eb:52:7a:d2:35:da:89:96:f5:15:0e:85:46:e7 u6\@example.org u6\@example.org
\n\n";

###################################################################
# Deletion of keys.
try "
    DEF DEL = ssh %1 ukm del %2
    DEF DELOK  = DEL %1 %2; ok
    DEF DELNOK = DEL %1 %2; !ok
    DEF DELNOMGR = DELNOK %1 %2; /FATAL: You are not managing the key /
";

# Deletion requires a keyid.
try "ssh u3 ukm del; !ok; /FATAL: keyid required/";

# u3 can, of course, not remove any unmanaged key.
try "DELNOMGR u3 u2";

# But u3 can delete u4@example.org and u6@example.org. This will, of course,
# not remove the key u4@example.org that u2 manages.
try "
    DELOK u3 u4\@example.org
    DELOK u3 u6\@example.org
";

# After having deleted u4@example.org, u3 cannot remove it again,
# even though, u2 still manages that key.
try "DELNOMGR u3 u4\@example.org";

# Of course a super-key-manager can remove any (existing) key.
try "
    DELOK  admin zzz/guests/u2/u4\@example.org
    DELNOK admin zzz/guests/u2/u4\@example.org
        /FATAL: You are not managing the key zzz/guests/u2/u4\@example.org./
    DELNOK admin zzz/guests/u2/u4\@example.org\@x
        /FATAL: You are not managing the key zzz/guests/u2/u4\@example.org./
    DELOK  admin zzz/guests/u2/u4\@example.org\@foo
";

# As the admin could do that via pushing to the gitolite-admin manually,
# it's also allowed to delete even non-guest keys.
try "DELOK admin u3";

# Let's clean the environment again.
try "
    DELOK admin laptop/u4\@example.org\@home
    DELOK admin laptop/u4\@example.org
    DELOK admin        u4\@example.org\@home
    DELOK admin        u4\@example.org
    ADDOK u3 admin u3
 ";

# Currently the admin has just one key. It cannot be removed.
# But after adding another key, deletion should work fine.
try "
    DELNOK admin admin; /FATAL: You cannot delete your last key./
    ADDOK u6 admin second/admin; /Adding new public key for admin./
    DELOK admin admin
    DELNOK u6 admin; /FATAL: You are not managing the key admin./
    DELNOK u6 second/admin; /FATAL: You cannot delete your last key./
    ADDOK admin u6 admin; /Adding new public key for admin./
    DELOK u6 second/admin
";

###################################################################
# Selfkey management.

# If self key management is not switched on in the .gitolite.rc file,
# it's not allowed at all.
try "ssh u2 ukm add \@second; !ok; /FATAL: selfkey management is not enabled/";

# Let's enable it.
system("sed -i \"/'UKM_CONFIG'=>/s/=>{/=>{'SELFKEY_MANAGEMENT'=>1,/\" $h/.gitolite.rc");

# And add self-key-managers to gitolite.conf
# chdir("../gitolite-admin") or die "in `pwd`, could not cd ../g-a";
try "glt pull admin origin master; ok";
put "|cut -c5- > conf/gitolite.conf", '
    repo gitolite-admin
        RW+ = admin
    repo testing
        RW+ = @all
    @guest-key-managers = u2 u3
    @self-key-managers = u1 u2
    @creators = u2 u3
    repo pub/CREATOR/..*
        C   =   @creators
        RW+ =   CREATOR
        RW  =   WRITERS
        R   =   READERS
';
try "
    git add conf keydir; ok
    git commit -m selfkey; ok; /master.* selfkey/
";
try "PUSH admin; ok; gsh; /master -> master/; !/FATAL/" or die text();

# Now we can start with the tests.

# Only self key managers are allowed to use selfkey management.
# See variable @self-key-managers.
try "ssh u3 ukm add \@second; !ok; /FATAL: You are not a selfkey manager./";

# Cannot add keyid that are not alphanumeric.
try "ssh u1 ukm add \@second-key; !ok; /FATAL: keyid not allowed:/";

# Add a second key for u1, but leave it pending by not feeding in the
# session key. The new user can login, but he/she lives under a quite
# random gl_user name and thus is pretty much excluded from everything
# except permissions given to @all. If this new id calls ukm without
# providing the session key, this (pending) key is automatically
# removed from the system.
# If a certain keyid is in the system, then it cannot be added again.
try "
    ADDOK u4 u1 \@second
    ssh admin ukm; ok; /u1     zzz/self/u1/zzz-add-[a-z0-9]{32}-second-u1/
    ssh u1    ukm; ok; /u1     \@second .pending add./
    ADDNOK u4 u1 \@second; /FATAL: keyid already in use: \@second/
    ssh u4    ukm; ok; /pending keyid deleted: \@second/
    ssh admin ukm; ok; !/zzz/; !/second/
";

# Not providing a proper ssh public key will abort. Providing a good
# ssh public key, which is not a session key makes the key invalid.
# The key will, therefore, be deleted by this operation.
try "
    ADDOK u4 u1 \@second
    echo fake|ssh u4 ukm; !ok; /FATAL: does not seem to be a valid pubkey/
    cat $pd/u5.pub | ssh u4 ukm; ok;
        /session key not accepted/
        /pending keyid deleted: \@second/
";

# True addition of a new selfkey is done via piping it to a second ssh
# call that uses the new key to call ukm. Note that the first ssh must
# have completed its job before the second ssh is able to successfully
# log in. This can be done via sleep or via redirecting to a file and
# then reading from it.
try "
    # ADDOK u4 u1 \@second | (sleep 2; ssh u4 ukm); ok
    ADD u4 u1 \@second > session; ok
    cat session | ssh u4 ukm; ok;  /pending keyid added: \@second/
";

# u1 cannot add his/her initial key, since that key can never be
# confirmed via ukm, so it is forbidden altogether. In fact, u1 is not
# allowed to add any key twice.
try "
    ADDNOK u1 u1 \@first
       /FATAL: You cannot add a key that already belongs to you./
    ADDNOK u4 u1 \@first
       /FATAL: You cannot add a key that already belongs to you./
";

# u1 also can add more keys, but not under an existing keyid. That can
# be done by any of his/her identities (here we choose u4).
try "
    ADDNOK u5 u1 \@second; /FATAL: keyid already in use: \@second/
    ADD u5 u4 \@third > session; ok
    cat session | ssh u5 ukm; ok;  /pending keyid added: \@third/
";

# u2 cannot add the same key, but is allowed to use the same name (@third).
try "
    ADDNOK u5 u2 \@third; /FATAL: cannot add key/
        /Same key is already available under another userid./
    ADD u6 u2 \@third > session; ok
    cat session | ssh u6 ukm; ok;  /pending keyid added: \@third/
";

# u6 can schedule his/her own key for deletion, but cannot actually
# remove it. Trying to do so results in bringing back the key. Actual
# deletion must be confirmed by another key.
try "
    ssh u6 ukm del \@third; /prepare deletion of key \@third/
    ssh u2 ukm; ok; /u2     \@third .pending del./
    ssh u6 ukm; ok; /undo pending deletion of keyid \@third/
    ssh u6 ukm del \@third; /prepare deletion of key \@third/
    ssh u2 ukm del \@third; ok;  /pending keyid deleted: \@third/
";

# While in pending-deletion state, it's forbidden to add another key
# with the same keyid. It's also forbidden to add a key with the same
# fingerprint as the to-be-deleted key).
# A new key under another keyid, is OK.
try "
    ssh u1 ukm del \@third; /prepare deletion of key \@third/
    ADDNOK u4 u1 \@third; /FATAL: keyid already in use: \@third/
    ADDNOK u5 u1 \@fourth;
        /FATAL: You cannot add a key that already belongs to you./
    ADD u6 u1 \@fourth > session; ok
    ssh u1 ukm; ok;
        /u1     \@second/
        /u1     \@fourth .pending add./
        /u1     \@third .pending del./
";
# We can remove a pending-for-addition key (@fourth) by logging in
# with a non-pending key. Trying to do anything with key u5 (@third)
# will just bring it back to its normal state, but not change the
# state of any other key. As already shown above, using u6 (@fourth)
# without a proper session key, would remove it from the system.
# Here we want to demonstrate that key u1 can delete u6 immediately.
try "ssh u1 ukm del \@fourth; /pending keyid deleted: \@fourth/";

# The pending-for-deletion key @third can also be removed via the u4
# (@second) key.
try "ssh u4 ukm del \@third; ok; /pending keyid deleted: \@third/";

# Non-existing selfkeys cannot be deleted.
try "ssh u4 ukm del \@x; !ok; /FATAL: You are not managing the key \@x./";


================================================
FILE: contrib/triggers/IP-check
================================================
#!/bin/bash

# Check an IP before allowing access.

# This is also a generic example of how to add arbitrary checks at the PRE_GIT
# stage, in order to control fetch/clone as well, not just push operations
# (VREFs, in contrast, only work for pushes).

# Notice how repo-specific information is being passed to this code (bullet 3
# below).  For more on that, see:
# https://gitolite.com/gitolite/dev-notes#appendix-1-repo-specific-environment-variables

# Instructions:

#   1.  put this in an appropriate triggers directory (read about non-core
#       code at http://gitolite.com/gitolite/non-core for more on this; the
#       cookbook may also help here).

#   2.  add a line:
#           PRE_GIT => [ 'IP-check' ],
#       just before the "ENABLE" line in the rc file

#   3.  add a line like this to the "repo ..." section in gitolite.conf:
#           option ENV.IP_allowed   =   1.2.3.0/24
#       take care that this expression is valid, in the sense that passing it
#       to 'ipcalc -n' will return the part before the "/".  I.e., in this
#       example, 'ipcalc -n 1.2.3.0/24' should (and does) return 1.2.3.0.

# ----

[ -n "$GL_OPTION_IP_allowed" ] || exit 0

expected=${GL_OPTION_IP_allowed%/*}
    mask=${GL_OPTION_IP_allowed#*/}

current_ip=${SSH_CONNECTION%% *}

eval `ipcalc -n $current_ip/$mask`

[ "$expected" == "$NETWORK" ] && exit 0

echo >&2 "IP $current_ip does not match allowed block $GL_OPTION_IP_allowed"
exit 1


================================================
FILE: contrib/triggers/file_mirror
================================================
#!/usr/bin/perl
use strict;
use warnings;

# Use an external (non-gitolite) mirror to backup gitolite repos.  They will
# be automatically kept uptodate as people push to your gitolite server.  If
# your server should die and you create a new one, you can quickly and easily
# get everything back from the external mirror with a few simple commands.

#       -------------------------------------------------------------
#       SEE WARNINGS/CAVEATS AND INSTRUCTIONS AT THE END OF THIS FILE
#       -------------------------------------------------------------

# ----------------------------------------------------------------------

use lib $ENV{GL_LIBDIR};
use Gitolite::Easy;

my ( $trigger, $repo, $dummy, $op ) = @ARGV;
exit 0 unless $trigger eq 'POST_GIT' or $trigger eq 'POST_CREATE';
exit 0 if     $trigger eq 'POST_GIT' and $op ne 'W';

chdir("$rc{GL_REPO_BASE}/$repo.git") or _die "chdir failed: $!\n";

my %config = config( $repo, "gitolite-options\\.mirror\\.extcopy" );
for my $copy ( values %config ) {
    _do($copy);

    # processing one copy is sufficient for restoring!
    last if $trigger eq 'POST_CREATE';
}

# in shell, that would be something like:
#   gitolite git-config -r $repo gitolite-options\\.mirror\\.extcopy | cut -f3 | while read copy
#   do
#       ...

# ----------------------------------------------------------------------

sub _do {
    my $url = shift;

    if ( $trigger eq 'POST_CREATE' ) {
        # brand new repo just created; needs to be populated from mirror

        # For your urls you will need a way to somehow query the server and
        # ask if the repo is present; it's upto you how you do it.
        my $path = $url;
        $path =~ s(^file://)();
        return unless -d $path;

        # now fetch.  Maybe we can put a "-q" in there?
        system( "git", "fetch", $url, "+refs/*:refs/*" );

    } elsif ( $trigger eq 'POST_GIT' ) {
        # someone just pushed; we need to update our mirrors

        # need to create the repo on the mirror.  Again, it's upto you how you
        # make sure there's a repo on the mirror that can receive the push.
        make_repo($url);    # in case it doesn't already exist

        # now push
        system( "git", "push", "--mirror", $url );
    }
}

sub make_repo {
    my $url = shift;
    # in this example, the URL is 'file:///...'; for other urls, presumably
    # the url tells you enough about how to *create* a repo.

    my $path = $url;
    $path =~ s(^file://)();
    return if -d $path;
    system( "git", "init", "--bare", $path );
}

__END__

WARNINGS
--------

1.  THIS IS SAMPLE CODE.  You will AT LEAST have to customise the _do() and
    make_repo() functions above based on what your remote URLs are.  For
    example, I don't even know how to create a repo from the command line if
    your external store is, say, github!

2.  THIS DOES NOT WORK FOR WILD REPOs.  It can be made to work, with a few
    extra steps to backup and restore the "gl-perms" and "gl-creator" files.

    "Left as an exercise for the reader!"

DESIGN NOTES
------------

This is really just a combination of "upstream" (see src/triggers/upstream)
and mirroring (gitolite mirroring does allow a copy to be non-gitolite, as
long as the ssh stuff is done the same way).

The main difference is that gitolite mirroring expects peers to all talk ssh,
whereas this method lets you use other protocols.  Specifically, since this
whole thing was started off by someone wanting to put his repos on s3
(apparently jgit can talk to s3 directly), you can modify the two functions to
deal with whatever remote server you have.

LANGUAGE
--------

This doesn't have to be in perl.  Shell equivalent for the only gitolite
specific code is supplied; the rest of the code is fairly straightforward.

SETUP
-----

1.  Put this code into your LOCAL_CODE directory under "triggers"; see
    non-core.html for details.

2.  Add these lines to your rc file, just before the ENABLE line.  (I'm
    assuming a v3.4 or later installation here).

        POST_CREATE => [ 'file_mirror' ],
        POST_GIT => [ 'file_mirror' ],

3.  Backup your rc file, since you may have other changes in it that you'll
    want to preserve.

4.  Do something like this in your gitolite.conf file:

        repo @all
            option mirror.extcopy-1    =   file:///tmp/he1/%GL_REPO.git
            option mirror.extcopy-2    =   file:///tmp/he2/%GL_REPO.git

    As you can see, since this is just for demo/test, we're using a couple of
    temp directories to serve as our "remotes" using the file:// protocol.

5.  Do a one-time manual sync of all the repos (subsequent syncs happen on
    each push):

        gitolite list-phy-repos | xargs -I xx gitolite trigger POST_GIT xx admin W

    (This is a little trick we're playing on the trigger stuff, but it should
    work fine.  Just make sure that, if you have other things in your POST_GIT
    trigger list, they're not affected in some way.  'gitolite query-rc
    POST_GIT' will tell you what else you have.)

That takes care of the "setup" and "regular backup".

RESTORE
-------

1.  Install gitolite normally.  You'll get the usual two repos.

2.  Restore the previously backed up rc file to replace the default one that
    gitolite created.  At the very least, the rc file should have the
    POST_CREATE and POST_GIT entries.

        ---------------------------------------------------------
        IF YOU FORGET THIS STEP, NASTY THINGS WILL HAPPEN TO YOU!
        ---------------------------------------------------------

3.  Clone the admin repo from one of your backup servers to some temp dir.  In
    our example,

        git clone /tmp/he1/gitolite-admin.git old-ga

4.  'cd' to that clone and force push to your *new* admin repo:

        cd old-ga
        git push -f admin:gitolite-admin

That's it.  As each repo gets created by the admin push, they'll get populated
by the backed up stuff due to the POST_CREATE trigger.


================================================
FILE: contrib/utils/ad_groups.sh
================================================
#!/bin/bash

# author derived from: damien.nozay@gmail.com
# author: Jonathan Gray

# Given a username,
# Provides a space-separated list of groups that the user is a member of.
#
# see http://gitolite.com/gitolite/conf.html#getting-user-group-info-from-ldap
# GROUPLIST_PGM => /path/to/ldap_groups.sh

# Be sure to add your domain CA to the trusted certificates in /etc/openldap/ldap.conf using the TLS_CACERT option or you'll get certificate validation errors

ldaphost='ldap://AD.DC1.local:3268,ldap://AD.DC2.local:3268,ldap://AD.DC3.local:3268'
ldapuser='git@domain.local'
ldappass='super.secret.password'
binddn='dc=domain,dc=local'
username=$1;

# I don't assume your users share a common OU, so I search the entire domain
ldap_groups() {
        # Go fetch the full user CN as it could be anywhere inside the DN
        usercn=$(
                ldapsearch -ZZ -H ${ldaphost} -D ${ldapuser} -w ${ldappass} -b ${binddn} -LLL -o ldif-wrap=no "(sAMAccountName=${username})" \
                | grep "^dn:" \
                | perl -pe 's|dn: (.*?)|\1|'
        )

        # Using a proprietary AD extension, let the AD Controller resolve all nested group memberships
        # http://ddkonline.blogspot.com/2010/05/how-to-recursively-get-group-membership.html
        # Also, substitute spaces in AD group names for '_' since gitolite expects a space separated list
        echo $(
                ldapsearch -ZZ -H ${ldaphost} -D ${ldapuser} -w ${ldappass} -b ${binddn} -LLL -o ldif-wrap=no "(member:1.2.840.113556.1.4.1941:=${usercn})" \
                | grep "^dn:" \
                | perl -pe 's|dn: CN=(.*?),.*|\1|' \
                | sed 's/ /_/g'
        )
}

ldap_groups $@


================================================
FILE: contrib/utils/gitolite-local
================================================
#!/bin/bash

# ----------------------------------------------------------------------
# change these lines to suit
testconf=$HOME/GITOLITE-TESTCONF
gitolite_url=https://github.com/sitaramc/gitolite
    # change it to something local for frequent use
    # gitolite_url=file:///tmp/gitolite.git

# ----------------------------------------------------------------------
# Usage: gitolite-local <options>
#
# Test your gitolite.conf rule lists on your LOCAL machine (without even
# pushing to the server!)
#
# (one-time)
#
#   1.  put this code somewhere in your $PATH if you wish
#   2.  edit the line near the top of the script if you want to use some other
#       directory than the default, for "testconf".
#   2.  prepare the "testconf" directory by running:
#           gitolite-local prep
#
# (lather, rinse, repeat)
#
#   1.  edit the conf (see notes below for more)
#           gitolite-local edit
#   2.  compile the conf
#           gitolite-local compile
#   3.  check permissions using "info" command:
#           gitolite-local info USERNAME
#   4.  check permissions using "access" command:
#           gitolite-local access <options for gitolite access command>
#   5.  clone, fetch, and push if you like!
#           gitolite-local clone <username> <reponame> <other options for clone>
#           gitolite-local fetch <username> <options for fetch>
#           gitolite-local push  <username> <options for push>
#
# note on editing the conf: you don't have to use the edit command; you can
# also directly edit '.gitolite/conf/gitolite.conf' in the 'testconf'
# directory.  You'll need to do that if your gitolite conf consists of more
# than just one file (like if you have includes, etc.)
#
# note on the clone command: most of the options won't work for clone, unless
# git is ok with them being placed *after* the repo name.

# ----------------------------------------------------------------------
die() { echo "$@" >&2; exit 1; }
usage() { perl -lne 'print substr($_, 2) if /^# Usage/../^$/' < $0; exit 1; }
[ -z "$1" ] && usage

# ----------------------------------------------------------------------
if [ $1 == prep ]
then
    set -e

    [ -d $testconf ] && die "directory '$testconf' already exists"

    mkdir $testconf
    cd $testconf

    export HOME=$PWD

    echo getting gitolite source...
    git clone $gitolite_url gitolite
    echo

    echo installing gitolite...
    gitolite/install >/dev/null
    echo

    echo setting up gitolite...
    export PATH=$PWD/gitolite/src:$PATH
    gitolite setup -a admin
    echo

    exit 0
fi

od=$PWD
cd $testconf
export HOME=$PWD
export PATH=$PWD/gitolite/src:$PATH

if [ $1 = edit ]
then
    editor=${EDITOR:-vim}
    $editor .gitolite/conf/gitolite.conf
elif [ $1 = compile ]
then
    gitolite compile
elif [ $1 = compile+ ]
then
    gitolite compile\; gitolite trigger POST_COMPILE
elif [ $1 = info ]
then
    shift
    user=$1
    shift

    GL_USER=$user gitolite info "$@"
elif [ $1 = access ]
then
    shift

    gitolite access "$@"
elif [ $1 = clone ]
then
    shift
    export G3T_USER=$1
    shift

    cd $od
    export GL_BINDIR=$HOME/gitolite/t
        # or you could do it the long way, using 'gitolite query-rc GL_BINDIR'
    repo=$1; shift
    git clone --upload-pack=$GL_BINDIR/gitolite-upload-pack file:///$repo "$@"
elif [ $1 = fetch ]
then
    shift
    export G3T_USER=$1
    shift

    cd $od
    export GL_BINDIR=$HOME/gitolite/t
    git fetch --upload-pack=$GL_BINDIR/gitolite-upload-pack "$@"
elif [ $1 = push ]
then
    shift
    export G3T_USER=$1
    shift

    cd $od
    export GL_BINDIR=$HOME/gitolite/t
    git push --receive-pack=$GL_BINDIR/gitolite-receive-pack "$@"
fi


================================================
FILE: contrib/utils/ipa_groups.pl
================================================
#!/usr/bin/env perl
#
# ipa_groups.pl
#
# See perldoc for usage
#
use Net::LDAP;
use Net::LDAP::Control::Paged;
use Net::LDAP::Constant qw(LDAP_CONTROL_PAGED);
use strict;
use warnings;

my $usage = <<EOD;
Usage: $0 \$uid
This script returns a list of groups that \$uid is a member of
EOD

my $uid = shift or die $usage;

## CONFIG SECTION

# If you want to do plain-text LDAP, then set ldap_opts to an empty hash and
# then set protocols of ldap_hosts to ldap://
my @ldap_hosts = [
  'ldaps://auth-ldap-001.prod.example.net',
  'ldaps://auth-ldap-002.prod.example.net',
];
my %ldap_opts = (
    verify => 'require',
    cafile => '/etc/pki/tls/certs/prod.example.net_CA.crt'
);

# Base DN to search
my $base_dn = 'dc=prod,dc=example,dc=net';

# User for binding to LDAP server with
my $user = 'uid=svc_gitolite_bind,cn=sysaccounts,cn=etc,dc=prod,dc=example,dc=net';
my $pass = 'reallysecurepasswordstringhere';

## Below variables should not need to be changed under normal circumstances

# OU where groups are located. Anything return that is not within this OU is
# removed from results. This OU is static on FreeIPA so will only need updating
# if you want to support other LDAP servers. This is a regex so can be set to
# anything you want (E.G '.*').
my $groups_ou = qr/cn=groups,cn=accounts,${base_dn}$/;

# strip path - if you want to return the full path of the group object then set
# this to 0
my $strip_group_paths = 1;

# Number of seconds before timeout (for each query)
my $timeout=5;

# user object class
my $user_oclass = 'person';

# group attribute
my $group_attrib = 'memberOf';

## END OF CONFIG SECTION

# Catch timeouts here
$SIG{'ALRM'} = sub {
  die "LDAP queries timed out";
};

alarm($timeout);

# try each server until timeout is reached, has very fast failover if a server
# is totally unreachable
my $ldap = Net::LDAP->new(@ldap_hosts, %ldap_opts) ||
  die "Error connecting to specified servers: $@ \n";

my $mesg = $ldap->bind(
    dn       => $user,
    password => $pass
);

if ($mesg->code()) {
  die ("error:",      $mesg->code(),"\n",
       "error name: ",$mesg->error_name(),"\n",
       "error text: ",$mesg->error_text(),"\n");
}

# How many LDAP query results to grab for each paged round
# Set to under 1000 to limit load on LDAP server
my $page = Net::LDAP::Control::Paged->new(size => 500);

# @queries is an array or array references. We initially fill it up with one
# arrayref (The first LDAP search) and then add more during the execution.
# First start by resolving the group.
my @queries = [ ( base    => $base_dn,
                  filter  => "(&(objectClass=${user_oclass})(uid=${uid}))",
                  control => [ $page ],
) ];

# array to store groups matching $groups_ou
my @verified_groups;

# Loop until @queries is empty...
foreach my $queryref (@queries) {

  # set cookie for paged querying
  my $cookie;
  alarm($timeout);
  while (1) {
    # Perform search
    my $mesg = $ldap->search( @{$queryref} );

    foreach my $entry ($mesg->entries) {
      my @groups = $entry->get_value($group_attrib);
      # find any groups matching $groups_ou  regex and push onto $verified_groups array
      foreach my $group (@groups) {
        if ($group =~ /$groups_ou/) {
          push @verified_groups, $group;
        }
      }
    }

    # Only continue on LDAP_SUCCESS
    $mesg->code and last;

    # Get cookie from paged control
    my($resp)  = $mesg->control(LDAP_CONTROL_PAGED) or last;
    $cookie    = $resp->cookie or last;

    # Set cookie in paged control
    $page->cookie($cookie);
  } # END: while(1)

  # Reset the page control for the next query
  $page->cookie(undef);

  if ($cookie) {
    # We had an abnormal exit, so let the server know we do not want any more
    $page->cookie($cookie);
    $page->size(0);
    $ldap->search( @{$queryref} );
    # Then die
    die("LDAP query unsuccessful");
  }

} # END: foreach my $queryref (...)

# we're assuming that the group object looks something like
# cn=name,cn=groups,cn=accounts,dc=X,dc=Y and there are no ',' chars in group
# names
if ($strip_group_paths) {
  for (@verified_groups) { s/^cn=([^,]+),.*$/$1/g };
}

foreach my $verified (@verified_groups) {
  print $verified . "\n";
}

alarm(0);

__END__

=head1 NAME

ipa_groups.pl

=head2 VERSION

0.1.1

=head2 DESCRIPTION

Connects to one or more FreeIPA-based LDAP servers in a first-reachable fashion and returns a newline separated list of groups for a given uid. Uses memberOf attribute and thus supports nested groups.

=head2 AUTHOR

Richard Clark <rclark@telnic.org>

=head2 FreeIPA vs Generic LDAP

This script uses regular LDAP, but is focussed on support for FreeIPA, where users and groups are generally contained within single OUs, and memberOf attributes within the user object are enumerated with a recursive list of groups that the user is a member of.

It is mostly impossible to provide generic out of the box LDAP support due to varying schemas, supported extensions and overlays between implementations.

=head2 CONFIGURATION

=head3  LDAP Bind Account 

To setup an LDAP bind user in FreeIPA, create a svc_gitolite_bind.ldif file along the following lines:

    dn: uid=svc_gitolite_bind,cn=sysaccounts,cn=etc,dc=prod,dc=example,dc=net
    changetype: add
    objectclass: account
    objectclass: simplesecurityobject
    uid: svc_gitolite_bind
    userPassword: reallysecurepasswordstringhere
    passwordExpirationTime: 20150201010101Z
    nsIdleTimeout: 0

Then create the service account user, using ldapmodify authenticating as the the directory manager account (or other acccount with appropriate privileges to the sysaccounts OU):

    $ ldapmodify -h auth-ldap-001.prod.example.net -Z -x -D "cn=Directory Manager" -W -f svc_gitolite_bind.ldif

=head3 Required Configuration

The following variables within the C<## CONFIG SECTION ##> need to be configured before the script will work.

C<@ldap_hosts> - Should be set to an array of URIs or hosts to connect to. Net::LDAP will attempt to connect to each host in this list and stop on the first reachable server. The example shows TLS-supported URIs, if you want to use plain-text LDAP then set the protocol part of the URI to LDAP:// or just provide hostnames as this is the default behavior for Net::LDAP.

C<%ldap_opts> - To use LDAP-over-TLS, provide the CA certificate for your LDAP servers. To use plain-text LDAP, then empty this hash of it's values or provide other valid arguments to Net::LDAP.

C<%base_dn> - This can either be set to the 'true' base DN for your directory, or alternatively you can set it the the OU that your users are located in (E.G cn=users,cn=accounts,dc=prod,dc=example,dc=net).

C<$user> - Provide the full Distinguished Name of your directory bind account as configured above.

C<$pass> - Set to password of your directory bind account as configured above.

=head3 Optional Configuration

C<$groups_ou> - By default this is a regular expression matching the default groups OU. Any groups not matching this regular expression are removed from the search results. This is because FreeIPA enumerates non-user type groups (E.G system, sudoers, policy and other types) within the memberOf attribute. To change this behavior, set C<$groups_ou> to a regex matching anything you want (E.G: '.*').

C<$strip_group_paths> - If this is set to perl boolean false (E.G '0') then groups will be returned in DN format. Default is true, so just the short/CN value is returned.

C<$timeout> - Number of seconds to wait for an LDAP query before determining that it has failed and trying the next server in the list. This does not affect unreachable servers, which are failed immediately.

C<$user_oclass> - Object class of the user to search for.

C<$group_attrib> - Attribute to search for within the user object that denotes the membership of a group.

=cut



================================================
FILE: contrib/utils/ldap_groups.sh
================================================
#!/bin/bash

# author: damien.nozay@gmail.com

# Given a username,
# Provides a space-separated list of groups that the user is a member of.
#
# see http://gitolite.com/gitolite/conf.html#getting-user-group-info-from-ldap
# GROUPLIST_PGM => /path/to/ldap_groups.sh

ldap_groups() {
    username=$1;
    # this relies on openldap / pam_ldap to be configured properly on your
    # system. my system allows anonymous search.
    echo $(
        ldapsearch -x -LLL "(&(objectClass=posixGroup)(memberUid=${username}))" cn \
        | grep "^cn" \
        | cut -d' ' -f2
    );
}

ldap_groups $@


================================================
FILE: contrib/utils/rc-format-v3.4
================================================
#!/usr/bin/perl

# help with rc file format change at v3.4 -- help upgrade v3 rc files from
# v3.3 and below to the new v3.4 and above format

# once you upgrade gitolite past 3.4, you may want to use the new rc file
# format, because it's really much nicer (just to recap: the old format will
# still work, in fact internally the new format gets converted to the old
# format before actually being used.  However, the new format makes it much
# easier to enable and disable features).

# PLEASE SEE WARNINGS BELOW

# this program helps you upgrade your rc file.

# STEPS
#   cd gitolite-source-repo-clone
#   contrib/utils/upgrade-rc33 /path/to/old.gitolite.rc > new.gitolite.rc

# WARNINGS
#   make sure you also READ ALL ERROR/WARNING MESSAGES GENERATED
#   make sure you EXAMINE THE FILE AND CHECK THAT EVERYTHING LOOKS GOOD before using it
#   be especially careful about
#       variables which contains single/double quotes or other special characters
#       variables that stretch across multiple lines
#       features which take arguments (like 'renice')
#       new features you've enabled which don't exist in the default rc

# ----------------------------------------------------------------------

use strict;
use warnings;
use 5.10.0;
use Cwd;
use Data::Dumper;
$Data::Dumper::Terse    = 1;
$Data::Dumper::Indent   = 1;
$Data::Dumper::Sortkeys = 1;

BEGIN {
    $ENV{HOME} = getcwd;
    $ENV{HOME} .= "/.home.rcupgrade.$$";
    mkdir $ENV{HOME} or die "mkdir '$ENV{HOME}': $!\n";
}

END {
    system("rm -rf ./.home.rcupgrade.$$");
}

use lib "./src/lib";
use Gitolite::Rc;
{
    no warnings 'redefine';
    sub Gitolite::Common::gl_log { }
}

# ----------------------------------------------------------------------

# everything happens inside a fresh v3.6.1+ gitolite clone; no other
# directories are used.

# the old rc file to be migrated is somewhere *else* and is supplied as a
# command line argument.

# ----------------------------------------------------------------------

my $oldrc = shift or die "need old rc filename as arg-1\n";

{

    package rcup;
    do $oldrc;
}

my %oldrc;
{
    no warnings 'once';
    %oldrc = %rcup::RC;
}

delete $rcup::{RC};
{
    my @extra = sort keys %rcup::;
    warn "**** WARNING ****\nyou have variables declared outside the %RC hash; you must handle them manually\n" if @extra;
}

# this is the new rc text being built up
my $newrc = glrc('default-text');

# ----------------------------------------------------------------------

# default disable all features in newrc
map { disable( $_, 'sq' ) } (qw(help desc info perms writable ssh-authkeys git-config daemon gitweb));
# map { disable($_, '') } (qw(GIT_CONFIG_KEYS));

set_s('HOSTNAME');
set_s( 'UMASK',               'num' );
set_s( 'GIT_CONFIG_KEYS',     'sq' );
set_s( 'LOG_EXTRA',           'num' );
set_s( 'DISPLAY_CPU_TIME',    'num' );
set_s( 'CPU_TIME_WARN_LIMIT', 'num' );
set_s('SITE_INFO');

set_s('LOCAL_CODE');

if ( $oldrc{WRITER_CAN_UPDATE_DESC} ) {
    die "tell Sitaram he changed the default rc too much" unless $newrc =~ /rc variables used by various features$/m;
    $newrc =~ s/(rc variables used by various features\n)/$1\n    # backward compat\n        WRITER_CAN_UPDATE_DESC      =>  1,\n/;

    delete $oldrc{WRITER_CAN_UPDATE_DESC};
}

if ( $oldrc{ROLES} ) {
    my $t = '';
    for my $r ( sort keys %{ $oldrc{ROLES} } ) {
        $t .= ( " " x 8 ) . $r . ( " " x ( 28 - length($r) ) ) . "=>  1,\n";
    }
    $newrc =~ s/(ROLES *=> *\{\n).*?\n( *\},)/$1$t$2/s;

    delete $oldrc{ROLES};
}

if ( $oldrc{DEFAULT_ROLE_PERMS} ) {
    warn "DEFAULT_ROLE_PERMS has been replaced by per repo option\nsee http://gitolite.com/gitolite/wild.html\n";
    delete $oldrc{DEFAULT_ROLE_PERMS};
}

# the following is a bit like the reverse of what the new Rc.pm does...

for my $l ( split /\n/, $Gitolite::Rc::non_core ) {
    next if $l =~ /^ *#/ or $l !~ /\S/;

    my ( $name, $where, $module ) = split ' ', $l;
    $module = $name if $module eq '.';
    ( $module = $name ) .= "::" . lc($where) if $module eq '::';

    # if you find $module as an element of $where, enable $name
    enable($name) if miw( $module, $where );
}

# now deal with commands
if ( $oldrc{COMMANDS} ) {
    for my $c ( sort keys %{ $oldrc{COMMANDS} } ) {
        if ( $oldrc{COMMANDS}{$c} == 1 ) {
            enable($c);
            # we don't handle anything else right (and so far only git-annex
            # is affected, as far as I remember)

            delete $oldrc{COMMANDS}{$c};
        }
    }
}

print $newrc;

for my $w (qw(INPUT POST_COMPILE PRE_CREATE ACCESS_1 POST_GIT PRE_GIT ACCESS_2 POST_CREATE SYNTACTIC_SUGAR)) {
    delete $oldrc{$w} unless scalar( @{ $oldrc{$w} } );
}
delete $oldrc{COMMANDS} unless scalar keys %{ $oldrc{COMMANDS} };

exit 0 unless %oldrc;

warn "the following parts of the old rc were NOT converted:\n";
print STDERR Dumper \%oldrc;

# ----------------------------------------------------------------------

# set scalars that the new file defaults to "commented out"
sub set_s {
    my ( $key, $type ) = @_;
    $type ||= '';
    return unless exists $oldrc{$key};

    # special treatment for UMASK
    $oldrc{$key} = substr( "00" . sprintf( "%o", $oldrc{$key} ), -4 ) if ( $key eq 'UMASK' );

    $newrc =~ s/# $key /$key   /;    # uncomment if needed
    if ( $type eq 'num' ) {
        $newrc =~ s/$key ( *=> *).*/$key $1$oldrc{$key},/;
    } elsif ( $type eq 'sq' ) {
        $newrc =~ s/$key ( *=> *).*/$key $1'$oldrc{$key}',/;
    } else {
        $newrc =~ s/$key ( *=> *).*/$key $1"$oldrc{$key}",/;
    }

    delete $oldrc{$key};
}

sub disable {
    my ( $key, $type ) = @_;
    if ( $type eq 'sq' ) {
        $newrc =~ s/^( *)'$key'/$1# '$key'/m;
    } else {
        $newrc =~ s/^( *)$key\b/$1# $key/m;
    }
}

sub enable {
    my $key = shift;
    $newrc =~ s/^( *)# *'$key'/$1'$key'/m;
    return if $newrc =~ /^ *'$key'/m;
    $newrc =~ s/(add new commands here.*\n)/$1            '$key',\n/;
}

sub miw {
    my ( $m, $w ) = @_;
    return 0 unless $oldrc{$w};
    my @in = @{ $oldrc{$w} };
    my @out = grep { !/^$m$/ } @{ $oldrc{$w} };
    $oldrc{$w} = \@out;
    return not scalar(@in) == scalar(@out);
}


================================================
FILE: contrib/utils/testconf
================================================
#!/bin/bash

# this is meant to be run on your *client* (where you edit and commit files
# in a gitolite-admin *working* repo), not on the gitolite server.
#
# TO USE
# ======

# To use this, first upgrade gitolite to the latest on the server; you need at
# least v3.6.7.
#
# Then, on the client:
#
#   1.  copy this file (contrib/utils/testconf in the latest gitolite) to
#       somewhere in your $PATH
#   2.  modify the following lines if you wish (default should be fine for
#       most people):

    # a semi-permanent area to play in (please delete it manually if you want to start afresh).
    testconf=$HOME/GITOLITE-TESTCONF
    # the gitolite source code
    gitolite_url=https://github.com/sitaramc/gitolite

#   3.  go to your gitolite-admin clone and make suitable changes; see example
#       below.  No need to push to the server, yet.
#   4.  run 'testconf`
#
# CAVEAT: include files are not handled the same way gitolite parsing handles
# them -- we just cat all the conf files together, in sorted order.
#
# If the tests ran OK, push your changes to the server as usual.

# EXAMPLE changes to gitolite.conf
# ================================
# Say you have these rules in the conf file:
#
#     repo foo
#         R       =   u1
#         RW      =   u2
#         RW+     =   u3
#
# To create test code for this, add the following lines to the conf file.
#
#     =begin testconf
#     # you can put arbitrary bash code here, but a simple example follows
#
#     ok() { "$@" && echo ok || echo "not ok ($*)"; }
#     nok() { ! "$@" && echo ok || echo "not ok ($*)"; }
#
#     ok gitolite access -q foo u1 R
#     nok gitolite access -q foo u1 W
#
#     ok gitolite access -q foo u2 W
#     nok gitolite access -q foo u2 +
#
#     ok gitolite access -q foo u3 +
#     =end
#
# Note that you can actually put in any bash code between the 'begin' and
# 'end' lines; the above is just a useful sample/template.
#
# Because of the 'begin' and 'end' lines, gitolite will ignore those lines
# when processing the conf file ON THE SERVER.
#
# (optional) TAP compliance
# =========================
# if you add a line 'echo 1..5' (in this case, since there are 5 ok/nok lines;
# you will certainly have more) to the top the file, you can run
#
#   prove `which testconf`
#
# which will give you a much nicer output.  The only issue is if you have
# include files, you will need to put that in the file whose name is sorted
# first!
#
# Using a non-default ".gitolite.rc"
# ==================================
#
# If your conf needs a non-default `~/.gitolite.rc`, copy the file you need as
# "testconf.gitolite.rc" in the root directory of the gitolite-admin clone
# where you are running "testconf".  (Whether you commit this file to the
# gitolite-admin repo, or keep it local/untracked, is your call).

# ----------------------------------------------------------------------
od=$PWD

# prep

mkdir -p $testconf
cd $testconf

export HOME=$PWD
export PATH=$PWD/gitolite/src:$PATH

[ -d gitolite ] || {

    echo getting gitolite source...
    git clone $gitolite_url gitolite
    echo

    echo installing gitolite...
    gitolite/install >/dev/null
    echo

    echo setting up gitolite...
    gitolite setup -a admin
    echo

}

# copy conf from $od

rm -rf           $testconf/.gitolite/conf
mkdir -p         $testconf/.gitolite/conf
cp -a $od/conf/* $testconf/.gitolite/conf/

# copy rc from $od, if it exists
[ -f $od/testconf.gitolite.rc ] && cp $od/testconf.gitolite.rc $testconf/.gitolite.rc

# compile+

gitolite compile
gitolite trigger POST_COMPILE

# snarf bits of code from conf files and run them

cat `find $testconf/.gitolite/conf -type f -name "*.conf" | sort` |
    perl -ne '
        print if /^=begin testconf$/ .. /^=end$/ and not /^=(begin|end)/;
    ' | /bin/bash


================================================
FILE: contrib/vim/indent/gitolite.vim
================================================
" Vim indent file
" Language:	gitolite configuration
" URL:		https://github.com/sitaramc/gitolite/blob/master/contrib/vim/indent/gitolite.vim
"	(https://raw.githubusercontent.com/sitaramc/gitolite/master/contrib/vim/indent/gitolite.vim)
" Maintainer:	Sitaram Chamarty <sitaramc@gmail.com>
" (former Maintainer:	Teemu Matilainen <teemu.matilainen@iki.fi>)
" Last Change:	2017 Oct 05

if exists("b:did_indent")
  finish
endif
let b:did_indent = 1

setlocal autoindent
setlocal indentexpr=GetGitoliteIndent()
setlocal indentkeys=o,O,*<Return>,!^F,=repo,\",=

" Only define the function once.
if exists("*GetGitoliteIndent")
  finish
endif

let s:cpo_save = &cpo
set cpo&vim

function! GetGitoliteIndent()
  let prevln = prevnonblank(v:lnum-1)
  let pline = getline(prevln)
  let cline = getline(v:lnum)

  if cline =~ '^\s*\(C\|R\|RW\|RW+\|RWC\|RW+C\|RWD\|RW+D\|RWCD\|RW+CD\|-\)[ \t=]'
    return shiftwidth()
  elseif cline =~ '^\s*config\s'
    return shiftwidth()
  elseif cline =~ '^\s*option\s'
    return shiftwidth()
  elseif pline =~ '^\s*repo\s' && cline =~ '^\s*\(#.*\)\?$'
    return shiftwidth()
  elseif cline =~ '^\s*#'
    return indent(prevln)
  elseif cline =~ '^\s*$'
    return -1
  else
    return 0
  endif
endfunction

let &cpo = s:cpo_save
unlet s:cpo_save


================================================
FILE: contrib/vim/syntax/gitolite.vim
================================================
" Vim syntax file
" Language:	gitolite configuration
" URL:		https://github.com/sitaramc/gitolite/blob/master/contrib/vim/syntax/gitolite.vim
"	(https://raw.githubusercontent.com/sitaramc/gitolite/master/contrib/vim/syntax/gitolite.vim)
" Maintainer:	Sitaram Chamarty <sitaramc@gmail.com>
" (former Maintainer:	Teemu Matilainen <teemu.matilainen@iki.fi>)
" Last Change:	2017 Oct 05

if exists("b:current_syntax")
  finish
endif

let s:cpo_save = &cpo
set cpo&vim

" this seems to be the best way, for now.
syntax sync fromstart

" ---- common stuff

syn match   gitoliteGroup           '@\S\+'

syn match   gitoliteComment         '#.*' contains=gitoliteTodo
syn keyword gitoliteTodo            TODO FIXME XXX NOT contained

" ---- main section

" catch template-data syntax appearing outside template-data section
syn match   gitoliteRepoError       '^\s*repo.*='
syn match   gitoliteRepoError       '^\s*\S\+\s*='  " this gets overridden later when first word is a perm, don't worry

" normal gitolite group and repo lines
syn match   gitoliteGroupLine       '^\s*@\S\+\s*=\s*\S.*$' contains=gitoliteGroup,gitoliteComment
syn match   gitoliteRepoLine        '^\s*repo\s\+[^=]*$' contains=gitoliteRepo,gitoliteGroup,gitoliteComment
syn keyword gitoliteRepo            repo contained

syn keyword gitoliteSpecialRepo     CREATOR

" normal gitolite rule lines
syn match   gitoliteRuleLine        '^\s*\(-\|C\|R\|RW+\?C\?D\?\)\s[^#]*' contains=gitoliteRule,gitoliteCreateRule,gitoliteDenyRule,gitoliteRefex,gitoliteUsers,gitoliteGroup
syn match   gitoliteRule            '\(^\s*\)\@<=\(-\|C\|R\|RW+\?C\?D\?\)\s\@=' contained
syn match   gitoliteRefex           '\(^\s*\(-\|R\|RW+\?C\?D\?\)\s\+\)\@<=\S.\{-}\(\s*=\)\@=' contains=gitoliteSpecialRefex
syn match   gitoliteSpecialRefex    'NAME/'
syn match   gitoliteSpecialRefex    '/USER/'
syn match   gitoliteCreateRule      '\(^\s*C\s.*=\s*\)\@<=\S[^#]*[^# ]' contained contains=gitoliteGroup
syn match   gitoliteDenyRule        '\(^\s*-\s.*=\s*\)\@<=\S[^#]*[^# ]' contained

" normal gitolite config (and similar) lines
syn match   gitoliteConfigLine      '^\s*\(config\|option\|include\|subconf\)\s[^#]*' contains=gitoliteConfigKW,gitoliteConfigKey,gitoliteConfigVal,gitoliteComment
syn keyword gitoliteConfigKW        config option include subconf contained
syn match   gitoliteConfigKey       '\(\(config\|option\)\s\+\)\@<=[^ =]*' contained
syn match   gitoliteConfigVal       '\(=\s*\)\@<=\S.*' contained

" ---- template-data section

syn region  gitoliteTemplateLine    matchgroup=PreProc start='^=begin template-data$' end='^=end$' contains=gitoliteTplRepoLine,gitoliteTplRoleLine,gitoliteGroup,gitoliteComment,gitoliteTplError

syn match   gitoliteTplRepoLine     '^\s*repo\s\+\S.*=.*' contained contains=gitoliteTplRepo,gitoliteTplTemplates,gitoliteGroup
syn keyword gitoliteTplRepo         repo contained
syn match   gitoliteTplTemplates    '\(=\s*\)\@<=\S.*' contained contains=gitoliteGroup,gitoliteComment

syn match   gitoliteTplRoleLine     '^\s*\S\+\s*=\s*.*' contained contains=gitoliteTplRole,gitoliteGroup,gitoliteComment
syn match   gitoliteTplRole         '\S\+\s*='he=e-1 contained

" catch normal gitolite rules appearing in template-data section
syn match   gitoliteTplError        '^\s*repo[^=]*$' contained
syn match   gitoliteTplError        '^\s*\(-\|R\|RW+\?C\?D\?\)\s'he=e-1 contained
syn match   gitoliteTplError        '^\s*\(config\|option\|include\|subconf\)\s'he=e-1 contained
syn match   gitoliteTplError        '^\s*@\S\+\s*=' contained contains=NONE

hi def link gitoliteGroup           Identifier
hi def link gitoliteComment         Comment
hi def link gitoliteTodo            ToDo
hi def link gitoliteRepoError       Error
hi def link gitoliteGroupLine       PreProc
hi def link gitoliteRepo            Keyword
hi def link gitoliteSpecialRepo     PreProc
hi def link gitoliteRule            Keyword
hi def link gitoliteCreateRule      PreProc
hi def link gitoliteDenyRule        WarningMsg
hi def link gitoliteRefex           Constant
hi def link gitoliteSpecialRefex    PreProc
hi def link gitoliteConfigKW        Keyword
hi def link gitoliteConfigKey       Identifier
hi def link gitoliteConfigVal       String
hi def link gitoliteTplRepo         Keyword
hi def link gitoliteTplTemplates    Constant
hi def link gitoliteTplRole         Constant
hi def link gitoliteTplError        Error

let b:current_syntax = "gitolite"

let &cpo = s:cpo_save
unlet s:cpo_save


================================================
FILE: convert-gitosis-conf
================================================
#!/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: install
================================================
#!/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;
use Config;

# 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.

    <perl-executable> ./install -to <dir>
        to copy the entire 'src' directory to <dir>, but will replace
        all of the shebangs with the path to <perl-executable>.  This
        is a way to force gitolite to use some perl that is not
        installed at /usr/bin/perl.

Simplest use, if $HOME/bin exists and is in $PATH, is:

    git clone https://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) {
    next unless $d;    # ignore empty values
    unless ( $d =~ m(^/) ) {
        print STDERR "FATAL: please use an absolute path, not a relative path\n";
        usage();
    }
    if ( not -d $d ) {
        print STDERR "FATAL: '$d' does not exist.\n";
        usage();
    }
}

chdir($ENV{GL_BINDIR});
my $version = `git describe --tags --long --dirty=-dt 2>/dev/null`;
unless ($version =~ /^v\d/) {
    print STDERR "git describe failed; cannot deduce version number\n";
    $version = "(unknown)";
}

if ($to) {
    _mkdir($to);
    system("cp -RpP * $to");
    _print( "$to/VERSION", $version );

    # Replace shebangs if necessary.
    my $thisperl = $Config{perlpath};
    if ($thisperl ne '/usr/bin/perl') {
        system("cd $to; grep -r -l /usr/bin/perl | xargs perl -pi -e 's(^#!/usr/bin/perl)(#!$thisperl)'");
    }
} 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 );
}


================================================
FILE: src/VREF/COUNT
================================================
#!/bin/sh

# gitolite VREF to count number of changed/new files in a push

# see gitolite docs for what the first 7 arguments mean

# inputs:
#   arg-8 is a number
#   arg-9 is optional, and can be "NEWFILES"
# outputs (STDOUT)
#   arg-7 if the number of changed (or new, if arg-9 supplied) files is > arg-8
#   otherwise nothing
# exit status:
#   always 0

die() { echo "$@" >&2; exit 1; }
[ -z "$8" ] && die "not meant to be run manually"

newsha=$3
oldtree=$4
newtree=$5
refex=$7

max=$8

nf=
[ "$9" = "NEWFILES" ] && nf='--diff-filter=A'
# NO_SIGNOFF implies NEWFILES
[ "$9" = "NO_SIGNOFF" ] && nf='--diff-filter=A'

# count files against all the other commits in the system not just $oldsha
# (why?  consider what is $oldtree when you create a new branch, or what is
# $oldsha when you update an old feature branch from master and then push it
count=`git log --name-only $nf --format=%n $newtree --not --all | grep . | sort -u | perl -ne '}{print "$."'`

[ $count -gt $max ] && {
    # count has been exceeded.  If $9 was NO_SIGNOFF there's still a chance
    # for redemption -- if the top commit has a proper signed-off by line
    [ "$9" = "NO_SIGNOFF" ] && {
        author_email=$(git log --format=%ae -1 $newsha)
        git cat-file -p $newsha |
            egrep -i >/dev/null "^ *$count +new +files +signed-off by: *$author_email *$" && exit 0
        echo $refex top commit message should include the text \'$count new files signed-off by: $author_email\'
        exit 0
    }
    echo -n $refex "(too many "
    [ -n "$nf" ] && echo -n "new " || echo -n "changed "
    echo "files in this push)"
}

exit 0


================================================
FILE: src/VREF/EMAIL-CHECK
================================================
#!/usr/bin/perl

# gitolite VREF to check if all *new* commits have author == pusher

#                       THIS IS NOT READY TO USE AS IS
#                       ------------------------------
#           you MUST change the 'email_ok()' sub to suit *YOUR* site's
#           gitolite username -> author email mapping!

# See bottom of the program for important philosophical notes.

use strict;
use warnings;

# mapping between gitolite userid and correct email address is encapsulated in
# this subroutine; change as you like
sub email_ok {
    my ($author_email) = shift;
    my $expected_email = "$ENV{GL_USER}\@atc.tcs.com";
    return $author_email eq $expected_email;
}

my ( $ref, $old, $new ) = @ARGV;
for my $rev (`git log --format="%ae\t%h\t%s" $new --not --all`) {
    chomp($rev);
    my ( $author_email, $hash, $subject ) = split /\t/, $rev;

    # again, we use the trick that a vref can just choose to die instead of
    # passing back a vref, having it checked, etc., if it's more convenient
    die "$ENV{GL_USER}, you can't push $hash authored by $author_email\n" . "\t(subject of commit was $subject)\n"
      unless email_ok($author_email);
}

exit 0;

__END__

The following discussion is for people who want to enforce this check on ALL
their developers (i.e., not just the newbies).

Doing this breaks the "D" in "DVCS", forcing all your developers to work to a
centralised model as far as pushes are concerned.  It prevents amending
someone else's commit and pushing (this includes rebasing, cherry-picking, and
so on, which are all impossible now).  It also makes *any* off-line
collabaration between two developers useless, because neither of them can push
the result to the server.

PHBs should note that validating the committer ID is NOT the same as reviewing
the code and running QA/tests on it.  If you're not reviewing/QA-ing the code,
it's probably worthless anyway.  Conversely, if you *are* going to review the
code and run QA/tests anyway, then you don't really need to validate the
author email!

In a DVCS, if you *pushed* a series of commits, you have -- in some sense --
signed off on them.  The most formal way to "sign" a series is to tack on and
push a gpg-signed tag, although most people don't go that far.  Gitolite's log
files are designed to preserve that accountability to *some* extent, though;
see contrib/adc/who-pushed for an admin defined command that quickly and
easily tells you who *pushed* a particular commit.

Anyway, the point is that the only purpose of this script is to

  * pander to someone who still has not grokked *D*VCS
          OR
  * tick off an item in some stupid PHB's checklist



================================================
FILE: src/VREF/FILETYPE
================================================
#!/bin/sh

# gitolite VREF to find autogenerated files

# *completely* site specific; use it as an illustration of what can be done
# with gitolite VREFs if you wish

# see gitolite docs for what the first 7 arguments mean

# inputs:
#   arg-8 is currently only one possible value: AUTOGENERATED
# outputs (STDOUT)
#   arg-7 if any files changed in the push look like they were autogenerated
#   otherwise nothing
# exit status:
#   always 0

die() { echo "$@" >&2; exit 1; }
[ -z "$8" ] && die "not meant to be run manually"

newsha=$3
oldtree=$4
newtree=$5
refex=$7

option=$8

[ "$option" = "AUTOGENERATED" ] && {
    # currently we only look for ".java" programs with the string "Generated
    # by the protocol buffer compiler.  DO NOT EDIT" in them.

    git log --name-only $nf --format=%n $newtree --not --all |
        grep . |
        sort -u |
        grep '\.java$' |
    while read fn
    do
        git show "$newtree:$fn" | egrep >/dev/null \
            'Generated by the protocol buffer compiler. +DO NOT EDIT' ||
            continue

        echo $refex
        exit 0
    done
}


================================================
FILE: src/VREF/MAX_NEWBIN_SIZE
================================================
#!/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 ];

exit 0 if $newsha eq '0000000000000000000000000000000000000000';

# / (.*) +\| 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


================================================
FILE: src/VREF/MERGE-CHECK
================================================
#!/usr/bin/perl
use strict;
use warnings;

# gitolite VREF to check if there are any merge commits in the current push.

# THIS IS DEMO CODE; please read all comments below as well as
# doc/vref.mkd before trying to use this.

# usage in conf/gitolite.conf goes like this:

#       -   VREF/MERGE-CHECK/master     =   @all
#       # reject only if the merge commit is being pushed to the master branch
#       -   VREF/MERGE-CHECK            =   @all
#       # reject merge commits to any branch

my $ref    = $ARGV[0];
my $oldsha = $ARGV[1];
my $newsha = $ARGV[2];
my $refex  = $ARGV[6];

# The following code duplicates some code from parse_conf_line() and some from
# check_ref().  This duplication is the only thing that is preventing me from
# removing the "M" permission code from 'core' gitolite and using this
# instead.  However, it does demonstrate how you would do this if you had to
# create any other similar features, for example someone wanted "no non-merge
# first-parent", which is far too specific for me to add to 'core'.

# -- begin duplication --
my $branch_refex = $ARGV[7] || '';
if ($branch_refex) {
    $branch_refex =~ m(^refs/) or $branch_refex =~ s(^)(refs/heads/);
} else {
    $branch_refex = 'refs/.*';
}
exit 0 unless $ref =~ /^$branch_refex/;
# -- end duplication --

# we can't run this check for tag creation or new branch creation, because
# 'git log' does not deal well with $oldsha = '0' x 40.
if ( $oldsha eq "0" x 40 or $newsha eq "0" x 40 ) {
    print STDERR "ref create/delete ignored for purposes of merge-check\n";
    exit 0;
}

my $ret = `git rev-list -n 1 --merges $oldsha..$newsha`;
print "$refex FATAL: merge commits not allowed\n" if $ret =~ /./;

exit 0;


================================================
FILE: src/VREF/NAME_NC
================================================
#!/bin/sh

# ----------------------------------------------------------------------
# VREF/NAME_NC
#   Like VREF/NAME, but only considers "new commits" -- i.e., commits that
#   don't already exist in the repo as part of some other ref.

# ----------------------------------------------------------------------
# WHY
#   VREF/NAME doesn't deal well with tag creation (or new branch creation),
#   since then all files in the project look like they are being created (due
#   to comparison with an empty tree).

#   Use this instead of VREF/NAME when you need to make that distinction.

newsha=$3

[ $newsha = "0000000000000000000000000000000000000000" ] && {
    echo "we don't currently handle deletions" >&2
    exit 1
}

git log --name-only --format=%n $newsha --not --all |
    sort -u | grep . | sed -e 's.^.VREF/NAME_NC/.'

# ----------------------------------------------------------------------
# OTHER NOTES
#   The built-in NAME does have a wee bit of a performance advantage.  I plan
#   to ignore this until someone notices this enough to be a problem :)
#
#   I could explain it here at least, but I fear that any explanation will
#   only add to the already rampant confusion about how VREFs work.  I'm not
#   rocking THAT boat again, sorry!


================================================
FILE: src/VREF/VOTES
================================================
#!/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


================================================
FILE: src/VREF/lock
================================================
#!/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


================================================
FILE: src/VREF/partial-copy
================================================
#!/bin/sh

# push updated branches back to the "main" repo.

# This must be run as the *last* VREF, though it doesn't matter what
# permission you give to it

die() { echo "$@" >&2; exit 1; }

repo=$GL_REPO
user=$GL_USER
ref=$1          # we're running like an update hook
old=$2
new=$3

# never send any STDOUT back, to avoid looking like a ref.  If we fail, git
# will catch it by our exit code
exec >&2

main=`git config --file $GL_REPO_BASE/$repo.git/config --get gitolite.partialCopyOf`;
[ -z "$main" ] && exit 0

rand=$$
export GL_BYPASS_ACCESS_CHECKS=1

if [ "$new" = "0000000000000000000000000000000000000000" ]
then
    # special case for deleting a ref (this is why it is important to put this
    # VREF as the last one; if we got this far he is allowed to delete it)
    git push -f $GL_REPO_BASE/$main.git :$ref || die "FATAL: failed to delete $ref"

    exit 0
fi

git push -f $GL_REPO_BASE/$main.git $new:refs/partial/br-$rand || die "FATAL: failed to send $new"

cd $GL_REPO_BASE/$main.git
git update-ref -d refs/partial/br-$rand
git update-ref $ref $new $old || die "FATAL: update-ref for $ref failed"

exit 0


================================================
FILE: src/VREF/refex-expr
================================================
#!/usr/bin/perl
use strict;
use warnings;

# see bottom of this file for instructons and IMPORTANT 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__

------------------------------------------------------------------------
IMPORTANT WARNINGS:
  * has not been tested heavily
      * SO PLEASE TEST YOUR SPECIFIC USE CASE THOROUGHLY!
      * read the NOTES section below
  * syntax and semantics are to be considered beta and may change as I find
    better use cases
------------------------------------------------------------------------

Refex expressions, like VREFs, are best used as additional "deny" rules, to
deny combinations that the normal ruleset cannot detect.

To enable this, uncomment 'refex-expr' in the ENABLE list in the rc file.

It allows you to say things like "don't allow users u3 and u4 to change the
Makefile in the master branch" (i.e., they can change any other file in
master, or the Makefile in any other branch, but not that specific combo).

    repo foo
        RW+                                 =   u1 u2   # line 1

        RW+ master                          =   u3 u4   # line 2
        RW+                                 =   u3 u4   # line 3
        RW+ VREF/NAME/Makefile              =   u3 u4   # line 4
        -   master and VREF/NAME/Makefile   =   u3 u4   # line 5

Line 5 is a "refex expression".  Here are the rules:

  * for each refex in the expression ("master" and "VREF/NAME/Makefile" in
    this example), a count is kept of the number of times the EXACT refex was
    matched and allowed in the *normal* rules (here, lines 2 and 4) during
    this push.

  * the expression is evaluated based on these counts.  0 is false, and
    any non-zero is true (see more examples later).  The truth value of the
    expression determines whether the refex expression matched.

    You can use any logical or arithmetic expression using refexes as operands
    and using these operators:

        not and or xor + - == -lt -gt -eq -le -ge -ne

    Parens are not allowed.  Precedence is as you might expect for those
    operators.  It's actually perl that is evaluating it (you can guess what
    the '-lt' etc., get translated to) so if in doubt, check 'man perlop'.

  * the refexes that form the terms of the expression (in this case, lines 2
    and 4) MUST come before the expression itself (i.e., line 5).

  * note the words "EXACT refex was matched" above.

    Let's say you add "u3" to line 1.  Then the refex expression in line 5
    would never match for u3.  This is because line 1 prevents line 2 from
    matching (being more general *and* appearing earlier), so the count for
    the "master" refex would be 0.  If "master" is 0 (false), then "master and
    <anything>" is also false.

    (Same thing is you swap lines 2 and 3; i.e., put the "RW+ = ..." before
    the "RW+ master = ...").

    Put another way, the terms in the refex expression are refexes, not refs.
    Merely pushing the master branch does not mean the count for "master"
    increases; it has to *match* on a line that has "master" as the refex.

Here are some more examples:

  * user u2 is allowed to push either 'doc/' or 'src/' but not both

        repo    foo
            RW+                         =   u1 u2 u3

            RW+ VREF/NAME/doc/                      =   u2
            RW+ VREF/NAME/src/                      =   u2
            -   VREF/NAME/doc/ and VREF/NAME/src/   =   u2

  * user u3 is allowed to push at most 2 files to conf/

        repo    foo
            RW+                         =   u1 u2 u3

            RW+ VREF/NAME/conf/         =   u3
            -   VREF/NAME/conf/ -gt 2   =   u3


================================================
FILE: src/commands/1plus1
================================================
#!/usr/bin/perl
use strict;
use warnings;

# import LOCK_*
use Fcntl qw(:flock);

my $lockbase      = shift;    # suggested: $GL_REPO_BASE/$GL_REPO.git/.gl-mirror-push-lock.$COPY_NAME
my @cmd_plus_args = @ARGV;    # the actual 'gitolite mirror ...' command
@ARGV = ();

# ----------------------------------------------------------------------

open( my $fhrun, ">", "$lockbase.run" ) or die "open '$lockbase.run' failed: $!";
if ( flock( $fhrun, LOCK_EX | LOCK_NB ) ) {
    # got run lock; you're good to go

    system(@cmd_plus_args);

    flock( $fhrun, LOCK_UN );
    exit 0;
}

# "run" lock failed; someone is already running the command

open( my $fhqueue, ">", "$lockbase.queue" ) or die "open '$lockbase.queue' failed: $!";
if ( flock( $fhqueue, LOCK_EX | LOCK_NB ) ) {
    # got queue lock, now block waiting for "run" lock
    flock( $fhrun, LOCK_EX );
    # got run lock, so take yourself out of "queue" state, then run
    flock( $fhqueue, LOCK_UN );

    system(@cmd_plus_args);

    flock( $fhrun, LOCK_UN );
    exit 0;
}

# "queue" lock also failed; someone is running AND someone is queued; we can go home
say STDERR "INFO: nothing to do/queue; '$lockbase' already running and 1 in queue";
exit 0;


================================================
FILE: src/commands/D
================================================
#!/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 owns "$repo" || 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


================================================
FILE: src/commands/access
================================================
#!/usr/bin/perl -s
use strict;
use warnings;

use lib $ENV{GL_LIBDIR};
use Gitolite::Rc;
use Gitolite::Common;
use Gitolite::Conf::Load;

our ( $q, $s, $h );    # quiet, show, help

=for usage
Usage:  gitolite access [-q|-s] <repo> <user> <perm> <ref>

Print access rights for arguments given.  The string printed has the word
DENIED in it if access was denied.  With '-q', returns only an exit code
(shell truth, not perl truth -- 0 is success).  For '-s', see below.

  - repo: mandatory
  - user: mandatory
  - perm: defauts to '+'.  Valid values: R, W, +, C, D, M
  - ref:  defauts to 'any'.  See notes below

Notes:
  - ref: something like 'master', or 'refs/tags/v1.0', or even a VREF if you
    know what they look like.

    The 'any' ref is special -- it ignores deny rules, thus simulating
    gitolite's behaviour during the pre-git access check (see 'deny-rules'
    section in rules.html for details).

  - batch mode: see src/triggers/post-compile/update-git-daemon-access-list
    for a good example that shows how to test several repos in one invocation.
    This is orders of magnitude faster than running the command multiple
    times; you'll notice if you have more than a hundred or so repos.

  - '-s' shows the rules (conf file name, line number, and rule) that were
    considered and how they fared.

  - you can also test the ability to create wild repos if you set GL_USER to
    the username and use ^C as the permission to check for.
=cut

usage() if not @ARGV >= 2 or $h;

my ( $repo, $user, $aa, $ref ) = @ARGV;
# default access is '+'
$aa  ||= '+';
# default ref is 'any'
$ref ||= 'any';
# fq the ref if needed
$ref =~ s(^)(refs/heads/) if $ref and $ref ne 'any' and $ref !~ m(^(refs|VREF)/);
_die "invalid perm" if not( $aa and $aa =~ /^(R|W|\+|C|D|M|\^C)$/ );
_die "invalid ref name" if not( $ref and $ref =~ $REF_OR_FILENAME_PATT );

my $ret = '';

if ( $repo ne '%' and $user ne '%' ) {
    # single repo, single user; no STDIN
    $ret = access( $repo, $user, adjust_aa($repo, $aa), $ref );

    show($ret) if $s;

    # adjust for fallthru in VREFs
    $ret =~ s/DENIED by fallthru/allowed by fallthru/ if $ref =~ m(^VREF/);

    if ( $ret =~ /DENIED/ ) {
        print "$ret\n" unless $q;
        exit 1;
    }

    print "$ret\n" unless $q;
    exit 0;
}

$repo = '' if $repo eq '%';
$user = '' if $user eq '%';

_die "'-q' and '-s' meaningless in pipe mode" if $q or $s;
@ARGV = ();
while (<>) {
    my @in = split;
    my $r  = $repo || shift @in;
    my $u  = $user || shift @in;
    $ret = access( $r, $u, adjust_aa($r, $aa), $ref );
    print "$r\t$u\t$ret\n";
}

sub adjust_aa {
    my ($repo, $aa) = @_;
    $aa = 'W' if $aa eq 'C' and not option($repo, 'CREATE_IS_C');
    $aa = '+' if $aa eq 'D' and not option($repo, 'DELETE_IS_D');
    $aa = 'W' if $aa eq 'M' and not option($repo, 'MERGE_CHECK');
    return $aa;
}

sub show {
    my $ret = shift;
    die "repo already exists; ^C won't work\n" if $ret =~ /DENIED by existence/;

    my $in = $rc{RULE_TRACE} or die "this should not happen! $ret";

    print STDERR "legend:";
    print STDERR "
    d => skipped deny rule due to ref unknown or 'any',
    r => skipped due to refex not matching,
    p => skipped due to perm (W, +, etc) not matching,
    D => explicitly denied,
    A => explicitly allowed,
    F => fallthru; access denied for normal refs, allowed for VREFs

";

    my %rule_info = read_ri($in);    # get rule info data for all traced rules
                                     # this means conf filename, line number, and content of the line

    # the rule-trace info is a set of pairs of a number plus a string.  Only
    # the last character in a string is valid (and has meanings shown above).
    # At the end there may be a final 'f'
    my @in = split ' ', $in;
    while (@in) {
        $in = shift @in;
        if ( $in =~ /^\d+$/ ) {
            my $res = shift @in or die "this should not happen either!";
            my $m = chop($res);
            printf "  %s %20s:%-6s %s\n", $m,
                                          $rule_info{$in}{fn},
                                          $rule_info{$in}{ln},
                                          $rule_info{$in}{cl};
        } elsif ( $in eq 'F' ) {
            printf "  %s %20s\n", $in, "(fallthru)";
        } else {
            die "and finally, this also should not happen!";
        }
    }
    print "\n";
}

sub read_ri {
    my %rules = map { $_ => 1 } $_[0] =~ /(\d+)/g;
    # contains a series of rule numbers, each of which we must search in
    # $GL_ADMIN_BASE/.gitolite/conf/rule_info

    my %rule_info;
    for ( slurp( $ENV{GL_ADMIN_BASE} . "/conf/rule_info" ) ) {
        my ( $r, $f, $l ) = split ' ', $_;
        next unless $rules{$r};
        $rule_info{$r}{fn} = $f;
        $rule_info{$r}{ln} = $l;
        $rule_info{$r}{cl} = conf_lines( $f, $l );

        # a wee bit of optimisation, in case the rule_info file is huge and
        # what we want is up near the beginning
        delete $rules{$r};
        last unless %rules;
    }
    return %rule_info;
}

{
    my %conf_lines;

    sub conf_lines {
        my ( $file, $line ) = @_;
        $line--;

        unless ( $conf_lines{$file} ) {
            $conf_lines{$file} = [ slurp( $ENV{GL_ADMIN_BASE} . "/conf/$file" ) ];
            chomp( @{ $conf_lines{$file} } );
        }
        return $conf_lines{$file}[$line];
    }
}


================================================
FILE: src/commands/compile-template-data
================================================
#!/usr/bin/perl
use strict;
use warnings;

# read template data to produce gl-perms and gl-repo-groups files in each
# $repo dir.  Create the repo if needed, using the wild repos create logic
# (with a "creator" of "gitolite-admin"!), though they're not really wild
# repos.

# see rule-templates.html in the gitolite documentation site.

# pure text manipulation (and very little of that!), no git or gitolite
# functions, no access checks, no possibility of a performance drama (or at
# least not a *complex* performance drama)

use lib $ENV{GL_LIBDIR};
use Gitolite::Rc;
use Gitolite::Common;
use Gitolite::Conf::Load;
use Gitolite::Conf::Store;

my $rb = $rc{GL_REPO_BASE};

@ARGV = `find $rc{GL_ADMIN_BASE}/conf -type f -name "*.conf" | sort`; chomp(@ARGV);
# we don't see the files in the exact same order that gitolite compile sees
# them, but we don't need to, for the data we are interested in (as long as
# you don't break up one repo's data across multiple files!)

# XXX We also potentially see more; a conf file may be in the directory, but
# not pulled in via an 'include' or 'subconf', so it doesn't exist as far as
# 'gitolite compile' is concerned, but here we *do* pull it in.

my $repos = '';
my $perms = '';
my $list = '';  # list of templates to apply
my $lip = '';   # line in progress
while (<>) {
    chomp;
    next unless /^=begin template-data$/ .. /^=end$/ and not /^=(begin|end)/;

    next unless /\S/;
    next if /^\s*#/;

    s/\t/ /g;   # all the same to us

    # handle continuation lines (backslash as last character)
    if (/\\$/) {
        s/\\$//;
        $lip .= $_;
        next;
    }
    $_ = $lip . $_;
    $lip = '';

    _warn("bad line: $_"), next if m([^ \w.\@/=-]);    # silently ignore lines that have characters we don't need
    if (/^\s*repo\s+(\S.*)=\s*(\S.*)$/) {
        flush($repos, $list, $perms);
        $repos = $1;
        $perms = '';
        $list = $2;

    } elsif (/^\s*(\S+)\s*=\s*(\S.*)$/) {
        $perms .= "$1 = $2\n";
    } else {
        # probably a blank line or a comment line.  If not, well *shrug*
    }
}
flush($repos, $list, $perms);

sub flush {
    my ($r, $l, $p) = @_;
    return unless $r and $l and $p;
    $l =~ s/\s+/ /g;

    my @r = split ' ', $r;
    while (@r) {
        my $r1 = shift @r;
        if ($r1 =~ m(^@)) {
            my @g = @{ Gitolite::Conf::Load::list_members($r1) };
            _warn "undefined group '$r1'" unless @g;
            unshift @r, @g;
            next;
        }

        flush_1($r1, $l, $p);
    }
}
sub flush_1 {
    my ($repo, $list, $perms) = @_;

    # beware of wild characters!
    return unless $repo =~ $REPONAME_PATT;

    if (not -d "$rb/$repo.git") {
        new_wild_repo( $repo, 'gitolite-admin', 'template-data' );
    }

    _print("$rb/$repo.git/gl-repo-groups", $list);

    _print("$rb/$repo.git/gl-perms", $perms);
}


================================================
FILE: src/commands/config
================================================
#!/usr/bin/perl
use 5.10.0;

# ---- WARNING ----

# If your site makes a distinction between "right to push the admin repo" and
# "right to run arbitrary commands on the server" (i.e., if not all of your
# "admins" have shell access to the server), this is a security risk. If that
# is the case, DO NOT ENABLE THIS COMMAND.

# ----------------------------------------------------------------------
# gitolite command to allow "git config" on repos (with some restrictions)

# (Not to be confused with the 'git-config' command, which is used only in
# server-side scripts, not remotely.)

# setup:
#   1.  Enable the command by adding it to the COMMANDS section in the ENABLE
#       list in the rc file.  (Have you read the warning above?)
#
#   2.  Specify configs allowed to be changed by the user.  This is a space
#       separated regex list.  For example:

#           repo ...
#               ... (various rules) ...
#               option user-configs = hook\..* foo.bar[0-9].*

use strict;
use warnings;

use lib $ENV{GL_LIBDIR};
use Gitolite::Easy;
use Gitolite::Common;

# ----------------------------------------------------------------------
# usage

=for usage
Usage:    ssh git@host config <repo> [git config options]

Runs "git config" in the repo.  Only the following 3 syntaxes are supported
(see 'man git-config'):

          --add name value
      --get-all name
    --unset-all name
         --list

Your administrator should tell you what keys are allowed for the "name".
=cut

# ----------------------------------------------------------------------
# arg checks

my %nargs = qw(
          --add 3
      --get-all 2
    --unset-all 2
         --list 1
     );

usage() if not @ARGV or $ARGV[0] eq '-h';

my $repo = shift;

my $op = shift;
usage() unless $op and exists $nargs{$op};

# ----------------------------------------------------------------------
# authorisation checks

die "sorry, you are not authorised\n" unless
    owns($repo)
        or
    ( ( $op eq '--get-all' or $op eq '--list' )
        ? can_read($repo)
        : ( can_write($repo) and option( $repo, 'writer-is-owner' ) )
    );

# ----------------------------------------------------------------------
# key validity checks

unless ($op eq '--list') {
    my $key = shift;

    my $val = '';
    $val = join(" ", @ARGV) if @ARGV;
    # values with spaces embedded get flattened by sshd when it passes
    # SSH_ORIGINAL_COMMAND to gitolite.  In this specific instance, we will
    # pretend we know what the user meant, and join up the last 1+ args into
    # one space-separated arg.

    my $user_configs = option( $repo, 'user-configs' );
    # this is a space separated list of allowed config keys
    my @validkeys = split( ' ', ( $user_configs || '' ) );
    my @matched = grep { $key =~ /^$_$/i } @validkeys;
    _die "config '$key' not allowed\n" if ( @matched < 1 );

    @ARGV = ($key);
    push @ARGV, $val if $val;
}

# ----------------------------------------------------------------------
# go!

unshift @ARGV, $op;
usage() unless @ARGV == $nargs{$op};

_chdir("$rc{GL_REPO_BASE}/$repo.git");
_system( "git", "config", @ARGV );


================================================
FILE: src/commands/create
================================================
#!/usr/bin/perl
use strict;
use warnings;

use lib $ENV{GL_LIBDIR};
use Gitolite::Rc;
use Gitolite::Common;
use Gitolite::Conf::Load;
use Gitolite::Conf::Store;

=for usage
create -- create a wild repo.

Usage:
    ssh git@host create <repo>
=cut

usage() if @ARGV != 1 or $ARGV[0] eq '-h';

$ENV{GL_USER} or _die "GL_USER not set";

my $repo = shift;
_die "invalid repo '$repo'" unless $repo =~ $REPONAME_PATT;

my $ret = access( $repo, $ENV{GL_USER}, '^C', 'any' );
_die "repo already exists or you are not authorised to create it" if $ret =~ /DENIED/;

new_wild_repo( $repo, $ENV{GL_USER}, 'create' );
gl_log( 'create', $repo, $ENV{GL_USER}, 'create' );


================================================
FILE: src/commands/creator
================================================
#!/usr/bin/perl
use strict;
use warnings;

use lib $ENV{GL_LIBDIR};
use Gitolite::Rc;
use Gitolite::Common;
use Gitolite::Conf::Load;

=for usage
Usage:  gitolite creator [-n] <reponame> [<username>]

Print the creator name for the repo.  A '-n' suppresses the newline.

When an optional username is supplied, it checks if the user is the creator of
the repo and returns an exit code (shell truth, 0 for success) instead of
printing anything, which makes it possible to do this in shell:

    if gitolite creator someRepo someUser
    then
        ...
=cut

usage() if not @ARGV or $ARGV[0] eq '-h';
my $nl = "\n";
if ( $ARGV[0] eq '-n' ) {
    $nl = '';
    shift;
}
my $repo = shift;
my $user = shift || '';

my $creator = '';
$creator = creator($repo) if not repo_missing($repo);
if ($user) {
    exit 0 if $creator eq $user;
    exit 1;
}
return ( $creator eq $user ) if $user;
print "$creator$nl";


================================================
FILE: src/commands/desc
================================================
#!/usr/bin/perl
use strict;
use warnings;

use lib $ENV{GL_LIBDIR};
use Gitolite::Easy;

=for usage
Usage:    ssh git@host desc <repo>
          ssh git@host desc <repo> <description string>

Show or set description for repo.  You need to have write access to the repo
and the 'writer-is-owner' option must be set for the repo, or it must be a
user-created ('wild') repo and you must be the owner.
=cut

usage() if not @ARGV or @ARGV < 1 or $ARGV[0] eq '-h';

my $repo = shift;
my $text = join( " ", @ARGV );
my $file = 'description';

#<<<
_die "you are not authorized" unless
    ( not $text and can_read($repo) )   or
    (     $text and owns($repo) )       or
    (     $text and can_write($repo)    and ( $rc{WRITER_CAN_UPDATE_DESC} or option( $repo, 'writer-is-owner' ) ) );
#>>>

$text
  ? textfile( file => $file, repo => $repo, text => $text )
  : print textfile( file => $file, repo => $repo );

__END__

kernel.org needs 'desc' to be available to people who have "RW" or above, not
just the "creator".  In fact they need it for non-wild repos so there *is* no
creator.  To accommodate this, we created the WRITER_CAN_UPDATE_DESC rc
variable.

However, that has turned out to be a bit of a blunt instrument for people with
different types of wild repos -- they don't want to apply this to all of them.
It seems easier to do this as an option, so you may have it for one set of
"repo ..." and not have it for others.  And if you want it for the whole
system you'd just put it under "repo @all".

The new 'writer-is-owner' option is meant to cover desc, readme, and any other
repo-specific text file, so it's also a blunt instrument, though in a
different dimension :-)


================================================
FILE: src/commands/fork
================================================
#!/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

gitolite query-rc -q LOCAL_CODE && ln -sf `gitolite query-rc LOCAL_CODE`/hooks/common/* hooks
ln -sf `gitolite query-rc GL_ADMIN_BASE`/hooks/common/* hooks

# record where you came from
echo "$from" > gl-forked-from

# cache control, if rc says caching is on
gitolite query-rc -q CACHE && perl -I$GL_LIBDIR -MGitolite::Cache -e "cache_control('flush', '$to')";

# trigger post_create
gitolite trigger POST_CREATE $to $GL_USER fork


================================================
FILE: src/commands/git-annex-shell
================================================
#!/usr/bin/perl

use lib $ENV{GL_LIBDIR};
use Gitolite::Easy;

# This command requires unrestricted arguments, so add it to the ENABLE list
# like this:
#   'git-annex-shell ua',

# This requires git-annex version 20111016 or newer. Older versions won't
# be secure.

use strict;
use warnings;

# ignore @ARGV and look at the original unmodified command
my $cmd = $ENV{SSH_ORIGINAL_COMMAND};

# Expect commands like:
#   git-annex-shell 'configlist' '/~/repo'
#   git-annex-shell 'configlist' '/repo'
#   git-annex-shell 'sendkey' '/~/repo' 'key'
# The parameters are always single quoted, and the repo path is always
# the second parameter.
# Further parameters are not validated here (see below).
die "bad git-annex-shell command: $cmd"
  unless $cmd =~ m#^(git-annex-shell '\w+' ')/(?:\~/)?([0-9a-zA-Z][0-9a-zA-Z._\@/+-]*)('( .*|))$#;
my $start = $1;
my $repo  = $2;
my $end   = $3;
$repo =~ s/\.git$//;
die "I dont like some of the characters in $repo\n" unless $repo =~ $Gitolite::Rc::REPONAME_PATT;
die "I dont like absolute paths in $cmd\n" if $repo =~ /^\//;
die "I dont like '..' paths in $cmd\n"     if $repo =~ /\.\./;

# Modify $cmd, fixing up the path to the repo to include GL_REPO_BASE.
my $newcmd = "$start$rc{GL_REPO_BASE}/$repo$end";

# Rather than keeping track of which git-annex-shell commands
# require write access and which are readonly, we tell it
# when readonly access is needed.
if ( can_write($repo) ) {
} elsif ( can_read($repo) ) {
    $ENV{GIT_ANNEX_SHELL_READONLY} = 1;
} else {
    die "$repo $ENV{GL_USER} DENIED\n";
}
# Further limit git-annex-shell to safe commands (avoid it passing
# unknown commands on to git-shell)
$ENV{GIT_ANNEX_SHELL_LIMITED} = 1;

# Note that $newcmd does *not* get evaluated by the unix shell.
# Instead it is passed as a single parameter to git-annex-shell for
# it to parse and handle the command. This is why we do not need to
# fully validate $cmd above.
Gitolite::Common::gl_log( $ENV{SSH_ORIGINAL_COMMAND} );
exec "git-annex-shell", "-c", $newcmd;

__END__

INSTRUCTIONS... (NEED TO BE VALIDATED BY SOMEONE WHO KNOWS GIT-ANNEX WELL).

based on http://git-annex.branchable.com/tips/using_gitolite_with_git-annex/
ONLY VARIATIONS FROM THAT PAGE ARE WRITTEN HERE.

setup

  * in the ENABLE list in the rc file, add an entry like this:
        'git-annex-shell ua',

That should be it; everything else should be as in that page.


================================================
FILE: src/commands/git-config
================================================
#!/usr/bin/perl
use strict;
use warnings;

use Getopt::Long;
use lib $ENV{GL_LIBDIR};
use Gitolite::Rc;
use Gitolite::Common;
use Gitolite::Conf::Load;

=for usage
Usage:  gitolite git-config [-n] [-q] [-r] <repo> <key|pattern>

Print git config keys and values for the given repo.  The key is either a full
key, or, if '-r' is supplied, a regex that is applied to all available keys.

    -q          exit code only (shell truth; 0 is success)
    -n          suppress trailing newline when used as key (not pattern)
    -r          treat key as regex pattern (unanchored)
    -ev         print keys with empty values also (see below)

Examples:
    gitolite git-config repo gitweb.owner
    gitolite git-config -q repo gitweb.owner
    gitolite git-config -r repo gitweb

Notes:

1.  When the key is treated as a pattern, prints:

        reponame<tab>key<tab>value<newline>

    Otherwise the output is just the value.

2.  By default, keys with empty values (specified as "" in the conf file) are
    treated as non-existant.  Using '-ev' will print those keys also.  Note
    that this only makes sense when the key is treated as a pattern, where
    such keys are printed as:

        reponame<tab>key<tab><newline>

3.  Finally, see the advanced use section of 'gitolite access -h' -- you can
    do something similar here also:

        gitolite list-phy-repos | gitolite git-config -r % gitweb\\. | cut -f1 > ~/projects.list
=cut

usage() if not @ARGV;

my ( $help, $nonl, $quiet, $regex, $ev ) = (0) x 5;
GetOptions(
    'n'  => \$nonl,
    'q'  => \$quiet,
    'r'  => \$regex,
    'h'  => \$help,
    'ev' => \$ev,
) or usage();

my ( $repo, $key ) = @ARGV;
usage() unless $key;

my $ret = '';

if ( $repo ne '%' and $key ne '%' ) {
    # single repo, single key; no STDIN
    $key = "^\Q$key\E\$" unless $regex;

    $ret = git_config( $repo, $key, $ev );

    # if the key is not a regex, it should match at most one item
    _die "found more than one entry for '$key'" if not $regex and scalar( keys %$ret ) > 1;

    # unlike access, there's nothing to print if we don't find any matching keys
    exit 1 unless %$ret;

    if ($regex) {
        map { print "$repo\t$_\t" . $ret->{$_} . "\n" } sort keys %$ret unless $quiet;
    } else {
        map { print $ret->{$_} . ( $nonl ? "" : "\n" ) } sort keys %$ret unless $quiet;
    }
    exit 0;
}

$repo = '' if $repo eq '%';
$key  = '' if $key eq '%';

_die "'-q' doesn't go with using a pipe" if $quiet;
@ARGV = ();
while (<>) {
    my @in = split;
    my $r  = $repo || shift @in;
    my $k  = $key || shift @in;
    $k = "^\Q$k\E\$" unless $regex;
    $ret = git_config( $r, $k, $ev );
    next unless %$ret;
    map { print "$r\t$_\t" . $ret->{$_} . "\n" } sort keys %$ret;
}


================================================
FILE: src/commands/help
================================================
#!/usr/bin/perl
use strict;
use warnings;

use lib $ENV{GL_LIBDIR};
use Gitolite::Rc;
use Gitolite::Common;

=for usage
Usage:  ssh git@host help       # via ssh
        gitolite help           # directly on server command line

Prints a list of custom commands available at this gitolite installation.

Each command has (or should have!) its own help, accessed by passing it the
'-h' option; e.g., "gitolite access -h".

NOTE: These commands are found in the commands subdirectory (see the page on
"core and non-core gitolite" in the documentation).  Gitolite also has some
built-in commands; run just "gitolite", with no arguments, to see those.
=cut

usage() if @ARGV;

print gree
Download .txt
gitextract_ay9ki2ef/

├── CHANGELOG
├── CONTRIBUTING
├── COPYING
├── README.markdown
├── check-g2-compat
├── contrib/
│   ├── commands/
│   │   ├── compile-1
│   │   └── ukm
│   ├── hooks/
│   │   └── repo-specific/
│   │       └── save-push-signatures
│   ├── lib/
│   │   ├── Apache/
│   │   │   └── gitolite.conf
│   │   └── Gitolite/
│   │       └── Triggers/
│   │           └── RedmineUserAlias.pm
│   ├── t/
│   │   └── ukm.t
│   ├── triggers/
│   │   ├── IP-check
│   │   └── file_mirror
│   ├── utils/
│   │   ├── ad_groups.sh
│   │   ├── gitolite-local
│   │   ├── ipa_groups.pl
│   │   ├── ldap_groups.sh
│   │   ├── rc-format-v3.4
│   │   └── testconf
│   └── vim/
│       ├── indent/
│       │   └── gitolite.vim
│       └── syntax/
│           └── gitolite.vim
├── convert-gitosis-conf
├── install
├── src/
│   ├── VREF/
│   │   ├── COUNT
│   │   ├── EMAIL-CHECK
│   │   ├── FILETYPE
│   │   ├── MAX_NEWBIN_SIZE
│   │   ├── MERGE-CHECK
│   │   ├── NAME_NC
│   │   ├── VOTES
│   │   ├── lock
│   │   ├── partial-copy
│   │   └── refex-expr
│   ├── commands/
│   │   ├── 1plus1
│   │   ├── D
│   │   ├── access
│   │   ├── compile-template-data
│   │   ├── config
│   │   ├── create
│   │   ├── creator
│   │   ├── desc
│   │   ├── fork
│   │   ├── git-annex-shell
│   │   ├── git-config
│   │   ├── help
│   │   ├── htpasswd
│   │   ├── info
│   │   ├── list-dangling-repos
│   │   ├── lock
│   │   ├── mirror
│   │   ├── motd
│   │   ├── newbranch
│   │   ├── option
│   │   ├── owns
│   │   ├── perms
│   │   ├── print-default-rc
│   │   ├── push
│   │   ├── readme
│   │   ├── rsync
│   │   ├── sshkeys-lint
│   │   ├── sskm
│   │   ├── sudo
│   │   ├── svnserve
│   │   ├── symbolic-ref
│   │   ├── who-pushed
│   │   └── writable
│   ├── gitolite
│   ├── gitolite-shell
│   ├── lib/
│   │   └── Gitolite/
│   │       ├── Cache.pm
│   │       ├── Common.pm
│   │       ├── Conf/
│   │       │   ├── Explode.pm
│   │       │   ├── Load.pm
│   │       │   ├── Store.pm
│   │       │   └── Sugar.pm
│   │       ├── Conf.pm
│   │       ├── Easy.pm
│   │       ├── Hooks/
│   │       │   ├── PostUpdate.pm
│   │       │   └── Update.pm
│   │       ├── Rc.pm
│   │       ├── Setup.pm
│   │       ├── Test/
│   │       │   └── Tsh.pm
│   │       ├── Test.pm
│   │       ├── Triggers/
│   │       │   ├── Alias.pm
│   │       │   ├── AutoCreate.pm
│   │       │   ├── CpuTime.pm
│   │       │   ├── Kindergarten.pm
│   │       │   ├── Mirroring.pm
│   │       │   ├── Motd.pm
│   │       │   ├── RefexExpr.pm
│   │       │   ├── RepoUmask.pm
│   │       │   ├── Shell.pm
│   │       │   ├── TProxy.pm
│   │       │   └── Writable.pm
│   │       └── Triggers.pm
│   ├── syntactic-sugar/
│   │   ├── continuation-lines
│   │   ├── keysubdirs-as-groups
│   │   ├── macros
│   │   └── refex-expr
│   └── triggers/
│       ├── bg
│       ├── expand-deny-messages
│       ├── partial-copy
│       ├── post-compile/
│       │   ├── create-with-reference
│       │   ├── ssh-authkeys
│       │   ├── ssh-authkeys-shell-users
│       │   ├── ssh-authkeys-split
│       │   ├── update-description-file
│       │   ├── update-git-configs
│       │   ├── update-git-daemon-access-list
│       │   ├── update-gitweb-access-list
│       │   └── update-gitweb-daemon-from-options
│       ├── renice
│       ├── repo-specific-hooks
│       ├── set-default-roles
│       └── upstream
└── t/
    ├── 0-me-first.t
    ├── C-vs-C.t
    ├── README
    ├── access.t
    ├── all-yall.t
    ├── basic.t
    ├── branch-perms.t
    ├── daemon-gitweb-via-perms.t
    ├── deleg-1.t
    ├── deleg-2.t
    ├── deny-create.t
    ├── deny-rules-2.t
    ├── deny-rules.t
    ├── easy.t
    ├── fedora-root-smart-http-test-setup
    ├── fork.t
    ├── git-config.t
    ├── gitolite-receive-pack
    ├── gitolite-upload-pack
    ├── glt
    ├── hostname.t
    ├── include-subconf.t
    ├── info-json.t
    ├── info.t
    ├── invalid-refnames-filenames.t
    ├── keys/
    │   ├── admin
    │   ├── admin.pub
    │   ├── config
    │   ├── u1
    │   ├── u1.pub
    │   ├── u2
    │   ├── u2.pub
    │   ├── u3
    │   ├── u3.pub
    │   ├── u4
    │   ├── u4.pub
    │   ├── u5
    │   ├── u5.pub
    │   ├── u6
    │   └── u6.pub
    ├── listers.t
    ├── manjaro-root-smart-http-test-setup
    ├── merge-check.t
    ├── mirror-test
    ├── mirror-test-rc
    ├── mirror-test-setup.sh
    ├── mirror-test-ssh-config
    ├── partial-copy.t
    ├── perm-default-roles.t
    ├── perm-roles.t
    ├── perms-groups.t
    ├── personal-branches.t
    ├── reference.t
    ├── refex-expr-test-1
    ├── refex-expr-test-2
    ├── refex-expr-test-3
    ├── refex-expr-test-9
    ├── repo-specific-hooks.t
    ├── reset
    ├── rule-seq.t
    ├── sequence.t
    ├── smart-http
    ├── smart-http.root-setup
    ├── ssh-authkeys.t
    ├── ssh-basic.t
    ├── templates.t
    ├── vrefs-1.t
    ├── vrefs-2.t
    ├── wild-1.t
    ├── wild-2.t
    ├── writable.t
    └── z-end.t
Condensed preview — 186 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (739K chars).
[
  {
    "path": "CHANGELOG",
    "chars": 12480,
    "preview": "2025-07-18  v3.6.14 only one important fix:\n\n                    detect HEAD name in admin repo (don't require it be \"ma"
  },
  {
    "path": "CONTRIBUTING",
    "chars": 1322,
    "preview": "Go to http://gitolite.com/gitolite/index.html#contactsupport for information on\ncontacting me, the mailing list, and IRC"
  },
  {
    "path": "COPYING",
    "chars": 15170,
    "preview": "                    GNU GENERAL PUBLIC LICENSE\n                       Version 2, June 1991\n\n Copyright (C) 1989, 1991 Fr"
  },
  {
    "path": "README.markdown",
    "chars": 7473,
    "preview": "Gitolite README\n===============\n\n## about this README\n\n**(Github-users: click the \"wiki\" link before sending me anything"
  },
  {
    "path": "check-g2-compat",
    "chars": 3656,
    "preview": "#!/usr/bin/perl\n\nuse Cwd;\n\nmy $h  = $ENV{HOME};\nmy $rc = \"$h/.gitolite.rc\";\nmy %count;\n\nintro();\n\nmsg( FATAL => \"no rc f"
  },
  {
    "path": "contrib/commands/compile-1",
    "chars": 4587,
    "preview": "#!/usr/bin/perl -s\nuse strict;\nuse warnings;\n\n# DESCRIPTION:\n\n#   This program is meant to re-compile the access rules ("
  },
  {
    "path": "contrib/commands/ukm",
    "chars": 29591,
    "preview": "#!/usr/bin/perl\nuse strict;\nuse warnings;\n\nuse lib $ENV{GL_LIBDIR};\nuse Gitolite::Rc;\nuse Gitolite::Common;\nuse Gitolite"
  },
  {
    "path": "contrib/hooks/repo-specific/save-push-signatures",
    "chars": 8707,
    "preview": "#!/bin/sh\n\n# ----------------------------------------------------------------------\n# post-receive hook to adopt push ce"
  },
  {
    "path": "contrib/lib/Apache/gitolite.conf",
    "chars": 2306,
    "preview": "# Apache Gitolite smart-http install Active Directory Authentication\n\n# Author: Jonathan Gray\n\n# It is assumed you alrea"
  },
  {
    "path": "contrib/lib/Gitolite/Triggers/RedmineUserAlias.pm",
    "chars": 1259,
    "preview": "package Gitolite::Triggers::RedmineUserAlias;\n\nuse Gitolite::Rc;\nuse Gitolite::Common;\nuse Gitolite::Conf::Load;\n\nuse st"
  },
  {
    "path": "contrib/t/ukm.t",
    "chars": 17234,
    "preview": "#!/usr/bin/perl\n\n# Call like this:\n# TSH_VERBOSE=1 TSH_ERREXIT=1 HARNESS_ACTIVE=1 GITOLITE_TEST=y prove t/ukm.t\n\nuse str"
  },
  {
    "path": "contrib/triggers/IP-check",
    "chars": 1445,
    "preview": "#!/bin/bash\n\n# Check an IP before allowing access.\n\n# This is also a generic example of how to add arbitrary checks at t"
  },
  {
    "path": "contrib/triggers/file_mirror",
    "chars": 5966,
    "preview": "#!/usr/bin/perl\nuse strict;\nuse warnings;\n\n# Use an external (non-gitolite) mirror to backup gitolite repos.  They will\n"
  },
  {
    "path": "contrib/utils/ad_groups.sh",
    "chars": 1689,
    "preview": "#!/bin/bash\n\n# author derived from: damien.nozay@gmail.com\n# author: Jonathan Gray\n\n# Given a username,\n# Provides a spa"
  },
  {
    "path": "contrib/utils/gitolite-local",
    "chars": 3683,
    "preview": "#!/bin/bash\n\n# ----------------------------------------------------------------------\n# change these lines to suit\ntestc"
  },
  {
    "path": "contrib/utils/ipa_groups.pl",
    "chars": 7842,
    "preview": "#!/usr/bin/env perl\n#\n# ipa_groups.pl\n#\n# See perldoc for usage\n#\nuse Net::LDAP;\nuse Net::LDAP::Control::Paged;\nuse Net:"
  },
  {
    "path": "contrib/utils/ldap_groups.sh",
    "chars": 592,
    "preview": "#!/bin/bash\n\n# author: damien.nozay@gmail.com\n\n# Given a username,\n# Provides a space-separated list of groups that the "
  },
  {
    "path": "contrib/utils/rc-format-v3.4",
    "chars": 6187,
    "preview": "#!/usr/bin/perl\n\n# help with rc file format change at v3.4 -- help upgrade v3 rc files from\n# v3.3 and below to the new "
  },
  {
    "path": "contrib/utils/testconf",
    "chars": 3800,
    "preview": "#!/bin/bash\n\n# this is meant to be run on your *client* (where you edit and commit files\n# in a gitolite-admin *working*"
  },
  {
    "path": "contrib/vim/indent/gitolite.vim",
    "chars": 1277,
    "preview": "\" Vim indent file\n\" Language:\tgitolite configuration\n\" URL:\t\thttps://github.com/sitaramc/gitolite/blob/master/contrib/vi"
  },
  {
    "path": "contrib/vim/syntax/gitolite.vim",
    "chars": 4454,
    "preview": "\" Vim syntax file\n\" Language:\tgitolite configuration\n\" URL:\t\thttps://github.com/sitaramc/gitolite/blob/master/contrib/vi"
  },
  {
    "path": "convert-gitosis-conf",
    "chars": 3667,
    "preview": "#!/usr/bin/perl -w\n#\n# migrate gitosis.conf to gitolite.conf format\n#\n# Based on gl-conf-convert by: Sitaram Chamarty\n# "
  },
  {
    "path": "install",
    "chars": 2986,
    "preview": "#!/usr/bin/perl\nuse strict;\nuse warnings;\n\n# Clearly you don't need a program to make one measly symlink, but the git\n# "
  },
  {
    "path": "src/VREF/COUNT",
    "chars": 1623,
    "preview": "#!/bin/sh\n\n# gitolite VREF to count number of changed/new files in a push\n\n# see gitolite docs for what the first 7 argu"
  },
  {
    "path": "src/VREF/EMAIL-CHECK",
    "chars": 2660,
    "preview": "#!/usr/bin/perl\n\n# gitolite VREF to check if all *new* commits have author == pusher\n\n#                       THIS IS NO"
  },
  {
    "path": "src/VREF/FILETYPE",
    "chars": 1099,
    "preview": "#!/bin/sh\n\n# gitolite VREF to find autogenerated files\n\n# *completely* site specific; use it as an illustration of what "
  },
  {
    "path": "src/VREF/MAX_NEWBIN_SIZE",
    "chars": 1123,
    "preview": "#!/usr/bin/perl\nuse strict;\nuse warnings;\n\n# gitolite VREF to check max size of new binary files\n\n# see gitolite docs fo"
  },
  {
    "path": "src/VREF/MERGE-CHECK",
    "chars": 1707,
    "preview": "#!/usr/bin/perl\nuse strict;\nuse warnings;\n\n# gitolite VREF to check if there are any merge commits in the current push.\n"
  },
  {
    "path": "src/VREF/NAME_NC",
    "chars": 1257,
    "preview": "#!/bin/sh\n\n# ----------------------------------------------------------------------\n# VREF/NAME_NC\n#   Like VREF/NAME, b"
  },
  {
    "path": "src/VREF/VOTES",
    "chars": 2983,
    "preview": "#!/bin/sh\n\n# gitolite VREF to count votes before allowing pushes to certain branches.\n\n# This approximates gerrit's voti"
  },
  {
    "path": "src/VREF/lock",
    "chars": 897,
    "preview": "#!/usr/bin/perl\nuse strict;\nuse warnings;\n\nuse lib $ENV{GL_LIBDIR};\nuse Gitolite::Common;\n\n# gitolite VREF to lock and u"
  },
  {
    "path": "src/VREF/partial-copy",
    "chars": 1127,
    "preview": "#!/bin/sh\n\n# push updated branches back to the \"main\" repo.\n\n# This must be run as the *last* VREF, though it doesn't ma"
  },
  {
    "path": "src/VREF/refex-expr",
    "chars": 3991,
    "preview": "#!/usr/bin/perl\nuse strict;\nuse warnings;\n\n# see bottom of this file for instructons and IMPORTANT WARNINGS!\n# ---------"
  },
  {
    "path": "src/commands/1plus1",
    "chars": 1215,
    "preview": "#!/usr/bin/perl\nuse strict;\nuse warnings;\n\n# import LOCK_*\nuse Fcntl qw(:flock);\n\nmy $lockbase      = shift;    # sugges"
  },
  {
    "path": "src/commands/D",
    "chars": 4433,
    "preview": "#!/bin/sh\n\n# ----------------------------------------------------------------------\n# ADMINISTRATOR NOTES:\n# -----------"
  },
  {
    "path": "src/commands/access",
    "chars": 5401,
    "preview": "#!/usr/bin/perl -s\nuse strict;\nuse warnings;\n\nuse lib $ENV{GL_LIBDIR};\nuse Gitolite::Rc;\nuse Gitolite::Common;\nuse Gitol"
  },
  {
    "path": "src/commands/compile-template-data",
    "chars": 2865,
    "preview": "#!/usr/bin/perl\nuse strict;\nuse warnings;\n\n# read template data to produce gl-perms and gl-repo-groups files in each\n# $"
  },
  {
    "path": "src/commands/config",
    "chars": 3141,
    "preview": "#!/usr/bin/perl\nuse 5.10.0;\n\n# ---- WARNING ----\n\n# If your site makes a distinction between \"right to push the admin re"
  },
  {
    "path": "src/commands/create",
    "chars": 657,
    "preview": "#!/usr/bin/perl\nuse strict;\nuse warnings;\n\nuse lib $ENV{GL_LIBDIR};\nuse Gitolite::Rc;\nuse Gitolite::Common;\nuse Gitolite"
  },
  {
    "path": "src/commands/creator",
    "chars": 903,
    "preview": "#!/usr/bin/perl\nuse strict;\nuse warnings;\n\nuse lib $ENV{GL_LIBDIR};\nuse Gitolite::Rc;\nuse Gitolite::Common;\nuse Gitolite"
  },
  {
    "path": "src/commands/desc",
    "chars": 1678,
    "preview": "#!/usr/bin/perl\nuse strict;\nuse warnings;\n\nuse lib $ENV{GL_LIBDIR};\nuse Gitolite::Easy;\n\n=for usage\nUsage:    ssh git@ho"
  },
  {
    "path": "src/commands/fork",
    "chars": 2258,
    "preview": "#!/bin/sh\n\n# Usage:    ssh git@host fork <repo1> <repo2>\n#\n# Forks repo1 to repo2.  You must have read permissions on re"
  },
  {
    "path": "src/commands/git-annex-shell",
    "chars": 2394,
    "preview": "#!/usr/bin/perl\n\nuse lib $ENV{GL_LIBDIR};\nuse Gitolite::Easy;\n\n# This command requires unrestricted arguments, so add it"
  },
  {
    "path": "src/commands/git-config",
    "chars": 2746,
    "preview": "#!/usr/bin/perl\nuse strict;\nuse warnings;\n\nuse Getopt::Long;\nuse lib $ENV{GL_LIBDIR};\nuse Gitolite::Rc;\nuse Gitolite::Co"
  },
  {
    "path": "src/commands/help",
    "chars": 1372,
    "preview": "#!/usr/bin/perl\nuse strict;\nuse warnings;\n\nuse lib $ENV{GL_LIBDIR};\nuse Gitolite::Rc;\nuse Gitolite::Common;\n\n=for usage\n"
  },
  {
    "path": "src/commands/htpasswd",
    "chars": 1332,
    "preview": "#!/usr/bin/perl\n\nuse strict;\nuse warnings;\n\nuse lib $ENV{GL_LIBDIR};\nuse Gitolite::Rc;\nuse Gitolite::Common;\n\n=for usage"
  },
  {
    "path": "src/commands/info",
    "chars": 4055,
    "preview": "#!/usr/bin/perl\nuse strict;\nuse warnings;\n\nuse Getopt::Long;\n\nuse lib $ENV{GL_LIBDIR};\nuse Gitolite::Rc;\nuse Gitolite::C"
  },
  {
    "path": "src/commands/list-dangling-repos",
    "chars": 1802,
    "preview": "#!/usr/bin/perl\nuse strict;\nuse warnings;\n\nuse lib $ENV{GL_LIBDIR};\nuse Gitolite::Common;\nuse Gitolite::Conf::Load;\n\n=fo"
  },
  {
    "path": "src/commands/lock",
    "chars": 4076,
    "preview": "#!/usr/bin/perl\nuse strict;\nuse warnings;\n\nuse Getopt::Long;\n\nuse lib $ENV{GL_LIBDIR};\nuse Gitolite::Rc;\nuse Gitolite::C"
  },
  {
    "path": "src/commands/mirror",
    "chars": 6117,
    "preview": "#!/usr/bin/perl\nuse strict;\nuse warnings;\n\nmy $tid;\n\nBEGIN {\n    $tid = $ENV{GL_TID} || 0;\n    delete $ENV{GL_TID};\n}\n\nu"
  },
  {
    "path": "src/commands/motd",
    "chars": 1539,
    "preview": "#!/usr/bin/perl\nuse strict;\nuse warnings;\n\nuse lib $ENV{GL_LIBDIR};\nuse Gitolite::Easy;\n\n=for usage\nUsage:    ssh git@ho"
  },
  {
    "path": "src/commands/newbranch",
    "chars": 1233,
    "preview": "#!/usr/bin/perl\nuse strict;\nuse warnings;\n\nuse lib $ENV{GL_LIBDIR};\nuse Gitolite::Easy;\n\n=for usage\nUsage:    ssh git@ho"
  },
  {
    "path": "src/commands/option",
    "chars": 4702,
    "preview": "#!/usr/bin/perl\n\n# ----------------------------------------------------------------------\n# gitolite command to allow re"
  },
  {
    "path": "src/commands/owns",
    "chars": 418,
    "preview": "#!/usr/bin/perl\nuse strict;\nuse warnings;\n\nuse lib $ENV{GL_LIBDIR};\nuse Gitolite::Easy;\n\n=for usage\nUsage:  gitolite own"
  },
  {
    "path": "src/commands/perms",
    "chars": 5443,
    "preview": "#!/usr/bin/perl\nuse strict;\nuse warnings;\n\nuse lib $ENV{GL_LIBDIR};\nuse Gitolite::Rc;\nuse Gitolite::Common;\nuse Gitolite"
  },
  {
    "path": "src/commands/print-default-rc",
    "chars": 115,
    "preview": "#!/usr/bin/perl\nuse strict;\nuse warnings;\n\nuse lib $ENV{GL_LIBDIR};\nuse Gitolite::Rc;\n\nprint glrc('default-text');\n"
  },
  {
    "path": "src/commands/push",
    "chars": 59,
    "preview": "#!/bin/sh\n\nexport GL_BYPASS_ACCESS_CHECKS=1\n\ngit push \"$@\"\n"
  },
  {
    "path": "src/commands/readme",
    "chars": 1626,
    "preview": "#!/usr/bin/perl\nuse strict;\nuse warnings;\n\nuse lib $ENV{GL_LIBDIR};\nuse Gitolite::Easy;\n\n# README.html files work simila"
  },
  {
    "path": "src/commands/rsync",
    "chars": 4085,
    "preview": "#!/usr/bin/perl\nuse strict;\nuse warnings;\n\nuse lib $ENV{GL_LIBDIR};\nuse Gitolite::Easy;\n\n=for admins\n\nBUNDLE SUPPORT\n\n  "
  },
  {
    "path": "src/commands/sshkeys-lint",
    "chars": 5103,
    "preview": "#!/usr/bin/perl\nuse strict;\nuse warnings;\n\n# complete rewrite of the sshkeys-lint program.  Usage has changed, see\n# usa"
  },
  {
    "path": "src/commands/sskm",
    "chars": 9604,
    "preview": "#!/usr/bin/perl\nuse strict;\nuse warnings;\n\nuse lib $ENV{GL_LIBDIR};\nuse Gitolite::Rc;\nuse Gitolite::Common;\n\n=for usage\n"
  },
  {
    "path": "src/commands/sudo",
    "chars": 694,
    "preview": "#!/bin/sh\n\n# Usage:    ssh git@host sudo <user> <command> <arguments>\n#\n# Let super-user run commands as any other user."
  },
  {
    "path": "src/commands/svnserve",
    "chars": 387,
    "preview": "#!/usr/bin/perl\n\nuse strict;\nuse warnings;\n\nuse lib $ENV{GL_LIBDIR};\nuse Gitolite::Rc;\nmy $svnserve = $rc{SVNSERVE} || '"
  },
  {
    "path": "src/commands/symbolic-ref",
    "chars": 1167,
    "preview": "#!/bin/sh\n\n# Usage:    ssh git@host symbolic-ref <repo> <arguments to git-symbolic-ref>\n#\n# allow 'git symbolic-ref' ove"
  },
  {
    "path": "src/commands/who-pushed",
    "chars": 5640,
    "preview": "#!/usr/bin/perl\nuse strict;\nuse warnings;\n\nuse lib $ENV{GL_LIBDIR};\nuse Gitolite::Easy;\n\nusage() if not @ARGV;\nusage($AR"
  },
  {
    "path": "src/commands/writable",
    "chars": 1742,
    "preview": "#!/usr/bin/perl\nuse strict;\nuse warnings;\n\nuse lib $ENV{GL_LIBDIR};\nuse Gitolite::Easy;\n\n=for usage\nUsage: gitolite writ"
  },
  {
    "path": "src/gitolite",
    "chars": 3136,
    "preview": "#!/usr/bin/perl\n\n# all gitolite CLI tools run as sub-commands of this command\n# ----------------------------------------"
  },
  {
    "path": "src/gitolite-shell",
    "chars": 9023,
    "preview": "#!/usr/bin/perl\n\n# gitolite shell, invoked from ~/.ssh/authorized_keys\n# -----------------------------------------------"
  },
  {
    "path": "src/lib/Gitolite/Cache.pm",
    "chars": 4368,
    "preview": "package Gitolite::Cache;\n\n# cache stuff using an external database (redis)\n# -------------------------------------------"
  },
  {
    "path": "src/lib/Gitolite/Common.pm",
    "chars": 13453,
    "preview": "package Gitolite::Common;\n\n# common (non-gitolite-specific) functions\n# ------------------------------------------------"
  },
  {
    "path": "src/lib/Gitolite/Conf/Explode.pm",
    "chars": 3250,
    "preview": "package Gitolite::Conf::Explode;\n\n# include/subconf processor\n# --------------------------------------------------------"
  },
  {
    "path": "src/lib/Gitolite/Conf/Load.pm",
    "chars": 20440,
    "preview": "package Gitolite::Conf::Load;\n\n# load conf data from stored files\n# ----------------------------------------------------"
  },
  {
    "path": "src/lib/Gitolite/Conf/Store.pm",
    "chars": 11378,
    "preview": "package Gitolite::Conf::Store;\n\n# receive parsed conf data and store it\n# ----------------------------------------------"
  },
  {
    "path": "src/lib/Gitolite/Conf/Sugar.pm",
    "chars": 5214,
    "preview": "# and now for something completely different...\n\npackage SugarBox;\n\nsub run_sugar_script {\n    my ( $ss, $lref ) = @_;\n "
  },
  {
    "path": "src/lib/Gitolite/Conf.pm",
    "chars": 3234,
    "preview": "package Gitolite::Conf;\n\n# explode/parse a conf file\n# -----------------------------------------------------------------"
  },
  {
    "path": "src/lib/Gitolite/Easy.pm",
    "chars": 6603,
    "preview": "package Gitolite::Easy;\n\n# easy access to gitolite from external perl programs\n# ---------------------------------------"
  },
  {
    "path": "src/lib/Gitolite/Hooks/PostUpdate.pm",
    "chars": 1935,
    "preview": "package Gitolite::Hooks::PostUpdate;\n\n# everything to do with the post-update hook\n# -----------------------------------"
  },
  {
    "path": "src/lib/Gitolite/Hooks/Update.pm",
    "chars": 5448,
    "preview": "package Gitolite::Hooks::Update;\n\n# everything to do with the update hook\n# --------------------------------------------"
  },
  {
    "path": "src/lib/Gitolite/Rc.pm",
    "chars": 22635,
    "preview": "package Gitolite::Rc;\n\n# everything to do with 'rc'.  Also defines some 'constants'\n# ----------------------------------"
  },
  {
    "path": "src/lib/Gitolite/Setup.pm",
    "chars": 5187,
    "preview": "package Gitolite::Setup;\n\n# implements 'gitolite setup'\n# --------------------------------------------------------------"
  },
  {
    "path": "src/lib/Gitolite/Test/Tsh.pm",
    "chars": 16447,
    "preview": "#!/usr/bin/perl\nuse 5.10.0;\n\n# Tsh -- non interactive Testing SHell in perl\n\n# TODO items:\n# - allow an RC file to be us"
  },
  {
    "path": "src/lib/Gitolite/Test.pm",
    "chars": 3323,
    "preview": "package Gitolite::Test;\n\n# functions for the test code to use\n# --------------------------------------------------------"
  },
  {
    "path": "src/lib/Gitolite/Triggers/Alias.pm",
    "chars": 4783,
    "preview": "package Gitolite::Triggers::Alias;\n\nuse Gitolite::Rc;\nuse Gitolite::Common;\nuse Gitolite::Conf::Load;\n\nuse strict;\nuse w"
  },
  {
    "path": "src/lib/Gitolite/Triggers/AutoCreate.pm",
    "chars": 728,
    "preview": "package Gitolite::Triggers::AutoCreate;\n\nuse strict;\nuse warnings;\n\n# perl trigger set for stuff to do with auto-creatin"
  },
  {
    "path": "src/lib/Gitolite/Triggers/CpuTime.pm",
    "chars": 1713,
    "preview": "package Gitolite::Triggers::CpuTime;\n\nuse Time::HiRes;\n\nuse Gitolite::Rc;\nuse Gitolite::Common;\n\nuse strict;\nuse warning"
  },
  {
    "path": "src/lib/Gitolite/Triggers/Kindergarten.pm",
    "chars": 2977,
    "preview": "package Gitolite::Triggers::Kindergarten;\n\n# http://www.great-quotes.com/quote/424177\n#   \"Doctor, it hurts when I do th"
  },
  {
    "path": "src/lib/Gitolite/Triggers/Mirroring.pm",
    "chars": 9271,
    "preview": "package Gitolite::Triggers::Mirroring;\n\nuse Gitolite::Rc;\nuse Gitolite::Common;\nuse Gitolite::Conf::Load;\n\nuse strict;\nu"
  },
  {
    "path": "src/lib/Gitolite/Triggers/Motd.pm",
    "chars": 685,
    "preview": "package Gitolite::Triggers::Motd;\n\nuse Gitolite::Rc;\nuse Gitolite::Common;\n\nuse strict;\nuse warnings;\n\n# print a message"
  },
  {
    "path": "src/lib/Gitolite/Triggers/RefexExpr.pm",
    "chars": 1888,
    "preview": "package Gitolite::Triggers::RefexExpr;\nuse strict;\nuse warnings;\n\n# track refexes passed and evaluate expressions on the"
  },
  {
    "path": "src/lib/Gitolite/Triggers/RepoUmask.pm",
    "chars": 1678,
    "preview": "package Gitolite::Triggers::RepoUmask;\n\nuse Gitolite::Rc;\nuse Gitolite::Common;\nuse Gitolite::Conf::Load;\n\nuse strict;\nu"
  },
  {
    "path": "src/lib/Gitolite/Triggers/Shell.pm",
    "chars": 1871,
    "preview": "package Gitolite::Triggers::Shell;\n\n# usage notes: uncomment 'Shell' in the ENABLE list in the rc file.\n\n# documentation"
  },
  {
    "path": "src/lib/Gitolite/Triggers/TProxy.pm",
    "chars": 3650,
    "preview": "package Gitolite::Triggers::TProxy;\n\n# ----------------------------------------------------------------------\n# transpar"
  },
  {
    "path": "src/lib/Gitolite/Triggers/Writable.pm",
    "chars": 439,
    "preview": "package Gitolite::Triggers::Writable;\n\nuse Gitolite::Rc;\nuse Gitolite::Common;\n\nsub access_1 {\n    my ( $repo, $aa, $res"
  },
  {
    "path": "src/lib/Gitolite/Triggers.pm",
    "chars": 698,
    "preview": "package Gitolite::Triggers;\n\n# load and run triggered modules\n# --------------------------------------------------------"
  },
  {
    "path": "src/syntactic-sugar/continuation-lines",
    "chars": 1121,
    "preview": "# vim: syn=perl:\n\n# \"sugar script\" (syntactic sugar helper) for gitolite3\n\n# Enabling this script in the rc file allows "
  },
  {
    "path": "src/syntactic-sugar/keysubdirs-as-groups",
    "chars": 820,
    "preview": "# vim: syn=perl:\n\n# \"sugar script\" (syntactic sugar helper) for gitolite3\n\n# Enabling this script in the rc file allows "
  },
  {
    "path": "src/syntactic-sugar/macros",
    "chars": 1713,
    "preview": "# vim: syn=perl:\n\n# \"sugar script\" (syntactic sugar helper) for gitolite3\n\n# simple line-wise macro processor\n# --------"
  },
  {
    "path": "src/syntactic-sugar/refex-expr",
    "chars": 916,
    "preview": "# vim: syn=perl:\n\n# \"sugar script\" (syntactic sugar helper) for gitolite3\n# --------------------------------------------"
  },
  {
    "path": "src/triggers/bg",
    "chars": 540,
    "preview": "#!/bin/bash\n\n# quick and dirty program to background any of the triggers programs that are\n# taking too long.  To use, j"
  },
  {
    "path": "src/triggers/expand-deny-messages",
    "chars": 5155,
    "preview": "#!/usr/bin/perl\nuse strict;\nuse warnings;\n\n$|++;\n\n# program name: expand-deny-messages\n\n# DOCUMENTATION IS AT THE BOTTOM"
  },
  {
    "path": "src/triggers/partial-copy",
    "chars": 2011,
    "preview": "#!/bin/sh\n\n# this is a wee bit expensive in terms of forks etc., compared to doing it in\n# perl, but I wanted to show ho"
  },
  {
    "path": "src/triggers/post-compile/create-with-reference",
    "chars": 855,
    "preview": "#!/usr/bin/perl\n\n# Set alternates if option reference.repo is set\n# ----------------------------------------------------"
  },
  {
    "path": "src/triggers/post-compile/ssh-authkeys",
    "chars": 4167,
    "preview": "#!/usr/bin/perl\nuse strict;\nuse warnings;\n\nuse Getopt::Long;\n\nuse lib $ENV{GL_LIBDIR};\nuse Gitolite::Rc;\nuse Gitolite::C"
  },
  {
    "path": "src/triggers/post-compile/ssh-authkeys-shell-users",
    "chars": 1505,
    "preview": "#!/usr/bin/perl\nuse strict;\nuse warnings;\n\nuse lib $ENV{GL_LIBDIR};\nuse Gitolite::Rc;\nuse Gitolite::Common;\n\n$|++;\n\nmy $"
  },
  {
    "path": "src/triggers/post-compile/ssh-authkeys-split",
    "chars": 2031,
    "preview": "#!/bin/bash\n\n#   split multi-key files into separate keys like ssh-authkeys likes\n\n# WHY\n# ---\n#\n# Yeah I wonder that to"
  },
  {
    "path": "src/triggers/post-compile/update-description-file",
    "chars": 738,
    "preview": "#!/bin/sh\n\n# For normal (not \"wild\") repos, gitolite v3 sets 'gitweb.description' instead\n# of putting the text in the \""
  },
  {
    "path": "src/triggers/post-compile/update-git-configs",
    "chars": 1613,
    "preview": "#!/usr/bin/perl\n\n# update git-config entries in each repo\n# ------------------------------------------------------------"
  },
  {
    "path": "src/triggers/post-compile/update-git-daemon-access-list",
    "chars": 1034,
    "preview": "#!/usr/bin/perl\n\n# update git-daemon-export-ok files in each repo\n# ----------------------------------------------------"
  },
  {
    "path": "src/triggers/post-compile/update-gitweb-access-list",
    "chars": 1551,
    "preview": "#!/bin/sh\n\n# this is literally the simplest gitweb update possible.  You are free to add\n# whatever you want and contrib"
  },
  {
    "path": "src/triggers/post-compile/update-gitweb-daemon-from-options",
    "chars": 1757,
    "preview": "#!/bin/sh\n\n# TODO: look at the commit in which *this* line was added, and see the changes\n# to the other scripts.  We ne"
  },
  {
    "path": "src/triggers/renice",
    "chars": 77,
    "preview": "#!/bin/sh\n\nn=$1\n[ \"$n\" = \"PRE_GIT\" ] && n=10\nrenice -n $n $GL_TID >/dev/null\n"
  },
  {
    "path": "src/triggers/repo-specific-hooks",
    "chars": 3449,
    "preview": "#!/usr/bin/perl\nuse strict;\nuse warnings;\n\n# setup repo-specific hooks\n\nuse lib $ENV{GL_LIBDIR};\nuse Gitolite::Rc;\nuse G"
  },
  {
    "path": "src/triggers/set-default-roles",
    "chars": 812,
    "preview": "#!/bin/sh\n\n# POST_CREATE trigger to set up default set of perms for a new wild repo\n\n# ---------------------------------"
  },
  {
    "path": "src/triggers/upstream",
    "chars": 2530,
    "preview": "#!/bin/sh\n\n# manage local, gitolite-controlled, copies of read-only upstream repos.\n\nrepo=$2\n\nurl=$(gitolite git-config "
  },
  {
    "path": "t/0-me-first.t",
    "chars": 3642,
    "preview": "#!/usr/bin/perl\nuse strict;\nuse warnings;\n\n# this is hardcoded; change it if needed\nuse lib \"src/lib\";\nuse Gitolite::Tes"
  },
  {
    "path": "t/C-vs-C.t",
    "chars": 1332,
    "preview": "#!/usr/bin/perl\nuse strict;\nuse warnings;\n\n# the commit message in which this test is introduced should have details, bu"
  },
  {
    "path": "t/README",
    "chars": 3572,
    "preview": "# instructions for running the tests\n\n# Pre-requisites\n\nInstall the following packages:\n\n*   Manjaro (and probably Arch)"
  },
  {
    "path": "t/access.t",
    "chars": 9877,
    "preview": "#!/usr/bin/perl\nuse strict;\nuse warnings;\n\n# this is hardcoded; change it if needed\nuse lib \"src/lib\";\nuse Gitolite::Tes"
  },
  {
    "path": "t/all-yall.t",
    "chars": 2148,
    "preview": "#!/usr/bin/perl\nuse strict;\nuse warnings;\n\n# this is hardcoded; change it if needed\nuse lib \"src/lib\";\nuse Gitolite::Tes"
  },
  {
    "path": "t/basic.t",
    "chars": 9171,
    "preview": "#!/usr/bin/perl\nuse strict;\nuse warnings;\n\n# this is hardcoded; change it if needed\nuse lib \"src/lib\";\nuse Gitolite::Tes"
  },
  {
    "path": "t/branch-perms.t",
    "chars": 4122,
    "preview": "#!/usr/bin/perl\nuse strict;\nuse warnings;\n\n# this is hardcoded; change it if needed\nuse lib \"src/lib\";\nuse Gitolite::Tes"
  },
  {
    "path": "t/daemon-gitweb-via-perms.t",
    "chars": 1721,
    "preview": "#!/usr/bin/perl\nuse strict;\nuse warnings;\n\n# this is hardcoded; change it if needed\nuse lib \"src/lib\";\nuse Gitolite::Tes"
  },
  {
    "path": "t/deleg-1.t",
    "chars": 2354,
    "preview": "#!/usr/bin/perl\nuse strict;\nuse warnings;\n\n# this is hardcoded; change it if needed\nuse lib \"src/lib\";\nuse Gitolite::Tes"
  },
  {
    "path": "t/deleg-2.t",
    "chars": 2767,
    "preview": "#!/usr/bin/perl\nuse strict;\nuse warnings;\n\n# this is hardcoded; change it if needed\nuse lib \"src/lib\";\nuse Gitolite::Tes"
  },
  {
    "path": "t/deny-create.t",
    "chars": 3412,
    "preview": "#!/usr/bin/perl\nuse strict;\nuse warnings;\n\n# this is hardcoded; change it if needed\nuse lib \"src/lib\";\nuse Gitolite::Tes"
  },
  {
    "path": "t/deny-rules-2.t",
    "chars": 2745,
    "preview": "#!/usr/bin/perl\nuse strict;\nuse warnings;\n\n# this is hardcoded; change it if needed\nuse lib \"src/lib\";\nuse Gitolite::Tes"
  },
  {
    "path": "t/deny-rules.t",
    "chars": 1045,
    "preview": "#!/usr/bin/perl\nuse strict;\nuse warnings;\n\n# this is hardcoded; change it if needed\nuse lib \"src/lib\";\nuse Gitolite::Tes"
  },
  {
    "path": "t/easy.t",
    "chars": 6277,
    "preview": "#!/usr/bin/perl\nuse strict;\nuse warnings;\n\n# this is hardcoded; change it if needed\nuse lib \"src/lib\";\nuse Gitolite::Eas"
  },
  {
    "path": "t/fedora-root-smart-http-test-setup",
    "chars": 3435,
    "preview": "#!/bin/bash\n\n# gitolite http mode TESTING setup for Fedora\n# - Probably works for CentOS also; if someone tests it let m"
  },
  {
    "path": "t/fork.t",
    "chars": 2877,
    "preview": "#!/usr/bin/perl\nuse strict;\nuse warnings;\n\n# this is hardcoded; change it if needed\nuse lib \"src/lib\";\nuse Gitolite::Tes"
  },
  {
    "path": "t/git-config.t",
    "chars": 3902,
    "preview": "#!/usr/bin/perl\nuse strict;\nuse warnings;\n\n# this is hardcoded; change it if needed\nuse lib \"src/lib\";\nuse Gitolite::Tes"
  },
  {
    "path": "t/gitolite-receive-pack",
    "chars": 298,
    "preview": "#!/usr/bin/perl\n\nuse strict;\nuse warnings;\nprint STDERR \"TRACE: grp(\", join( \")(\", @ARGV ), \")\\n\";\n\nmy $repo = shift;\n$r"
  },
  {
    "path": "t/gitolite-upload-pack",
    "chars": 297,
    "preview": "#!/usr/bin/perl\n\nuse strict;\nuse warnings;\nprint STDERR \"TRACE: gup(\", join( \")(\", @ARGV ), \")\\n\";\n\nmy $repo = shift;\n$r"
  },
  {
    "path": "t/glt",
    "chars": 1127,
    "preview": "#!/usr/bin/perl\nuse strict;\nuse warnings;\n\nuse FindBin;\nBEGIN { $ENV{GL_BINDIR} = $FindBin::RealBin; }\n\nmy $cmd  = shift"
  },
  {
    "path": "t/hostname.t",
    "chars": 2370,
    "preview": "#!/usr/bin/perl\nuse strict;\nuse warnings;\n\n# this is hardcoded; change it if needed\nuse lib \"src/lib\";\nuse Gitolite::Tes"
  },
  {
    "path": "t/include-subconf.t",
    "chars": 2789,
    "preview": "#!/usr/bin/perl\nuse strict;\nuse warnings;\n\n# this is hardcoded; change it if needed\nuse lib \"src/lib\";\nuse Gitolite::Tes"
  },
  {
    "path": "t/info-json.t",
    "chars": 4489,
    "preview": "#!/usr/bin/perl\nuse strict;\nuse warnings;\n\n# this is hardcoded; change it if needed\nuse lib \"src/lib\";\nuse Gitolite::Tes"
  },
  {
    "path": "t/info.t",
    "chars": 2302,
    "preview": "#!/usr/bin/perl\nuse strict;\nuse warnings;\n\n# this is hardcoded; change it if needed\nuse lib \"src/lib\";\nuse Gitolite::Tes"
  },
  {
    "path": "t/invalid-refnames-filenames.t",
    "chars": 1952,
    "preview": "#!/usr/bin/perl\nuse strict;\nuse warnings;\n\n# this is hardcoded; change it if needed\nuse lib \"src/lib\";\nuse Gitolite::Tes"
  },
  {
    "path": "t/keys/admin",
    "chars": 1679,
    "preview": "-----BEGIN RSA PRIVATE KEY-----\nMIIEpQIBAAKCAQEA0/X7uwd7xOvC3UTZaAnFOR5xqhdgcyc8vk3d1bXXthiuUSmq\n5t4uhS9qj0ismcPX0YRNhSD"
  },
  {
    "path": "t/keys/admin.pub",
    "chars": 404,
    "preview": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDT9fu7B3vE68LdRNloCcU5HnGqF2BzJzy+Td3Vtde2GK5RKarm3i6FL2qPSKyZw9fRhE2FIMSWi0bUpJan"
  },
  {
    "path": "t/keys/config",
    "chars": 320,
    "preview": "host *\n    stricthostkeychecking no\nhost admin\n        identityfile ~/.ssh/admin\n\nhost u? admin\n\tuser %USER\n\thostname lo"
  },
  {
    "path": "t/keys/u1",
    "chars": 1679,
    "preview": "-----BEGIN RSA PRIVATE KEY-----\nMIIEpQIBAAKCAQEAuB8HmJ5UX30xFktmvlLgSrzsGIzSiYiAYH8eU6epJGr/xjYD\n9GE6G9EcL+/NTc0ziPhIUtV"
  },
  {
    "path": "t/keys/u1.pub",
    "chars": 404,
    "preview": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC4HweYnlRffTEWS2a+UuBKvOwYjNKJiIBgfx5Tp6kkav/GNgP0YTob0Rwv781NzTOI+EhS1Wb6H6QUQe1D"
  },
  {
    "path": "t/keys/u2",
    "chars": 1679,
    "preview": "-----BEGIN RSA PRIVATE KEY-----\nMIIEpgIBAAKCAQEA4/t3WV4q98MlYV9Jbvbf7gE2Dzit6dD8xHsWBh2wTmggQM9I\n2RsPp1tTIoOpt4YdlTzV415"
  },
  {
    "path": "t/keys/u2.pub",
    "chars": 404,
    "preview": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDj+3dZXir3wyVhX0lu9t/uATYPOK3p0PzEexYGHbBOaCBAz0jZGw+nW1Mig6m3hh2VPNXjXnvsEotUy+ob"
  },
  {
    "path": "t/keys/u3",
    "chars": 1675,
    "preview": "-----BEGIN RSA PRIVATE KEY-----\nMIIEogIBAAKCAQEArK6uQkgoAnS+w6vs4DmvdOfcGdFUzh3ivHAXHug7sjjKGUB2\nKwcXNwcmJPtyNXM0BJ9ZoOJ"
  },
  {
    "path": "t/keys/u3.pub",
    "chars": 404,
    "preview": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCsrq5CSCgCdL7Dq+zgOa9059wZ0VTOHeK8cBce6DuyOMoZQHYrBxc3ByYk+3I1czQEn1mg4lKomjlxYsyX"
  },
  {
    "path": "t/keys/u4",
    "chars": 1679,
    "preview": "-----BEGIN RSA PRIVATE KEY-----\nMIIEpAIBAAKCAQEA2Wg3bl7T0C8VuR8HdbAqmwvQH4/T/maaqlQeJqcATRgWQNDv\nVthEasW5Kx8DzcSVRWS0cJ5"
  },
  {
    "path": "t/keys/u4.pub",
    "chars": 404,
    "preview": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDZaDduXtPQLxW5Hwd1sCqbC9Afj9P+ZpqqVB4mpwBNGBZA0O9W2ERqxbkrHwPNxJVFZLRwnkSlMsa+uzzh"
  },
  {
    "path": "t/keys/u5",
    "chars": 1675,
    "preview": "-----BEGIN RSA PRIVATE KEY-----\nMIIEowIBAAKCAQEA41A0bY6+0akNSJlR2PeRATNtncARXVOUar7CNaxwPqVXQR1+\nTmU9evmIEkRLf0kFAa7L3QD"
  },
  {
    "path": "t/keys/u5.pub",
    "chars": 404,
    "preview": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDjUDRtjr7RqQ1ImVHY95EBM22dwBFdU5RqvsI1rHA+pVdBHX5OZT16+YgSREt/SQUBrsvdAMWugW7iwOJI"
  },
  {
    "path": "t/keys/u6",
    "chars": 1675,
    "preview": "-----BEGIN RSA PRIVATE KEY-----\nMIIEowIBAAKCAQEAxyRjRT8RoSDnAnbZdrTjXhBMrkfNfolWFqc3qAjAo8Tmp7Ns\nn7R+KCl31RDkC9u4ll4AOfF"
  },
  {
    "path": "t/keys/u6.pub",
    "chars": 404,
    "preview": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDHJGNFPxGhIOcCdtl2tONeEEyuR81+iVYWpzeoCMCjxOans2yftH4oKXfVEOQL27iWXgA58X1sh0/2i8NW"
  },
  {
    "path": "t/listers.t",
    "chars": 2170,
    "preview": "#!/usr/bin/perl\nuse strict;\nuse warnings;\n\n# this is hardcoded; change it if needed\nuse lib \"src/lib\";\nuse Gitolite::Tes"
  },
  {
    "path": "t/manjaro-root-smart-http-test-setup",
    "chars": 4422,
    "preview": "#!/bin/bash\n\n# gitolite http mode TESTING setup for Manjaro\n# - Probably works for Arch also; if someone tests it let me"
  },
  {
    "path": "t/merge-check.t",
    "chars": 2635,
    "preview": "#!/usr/bin/perl\nuse strict;\nuse warnings;\n\n# this is hardcoded; change it if needed\nuse lib \"src/lib\";\nuse Gitolite::Tes"
  },
  {
    "path": "t/mirror-test",
    "chars": 10492,
    "preview": "#!/usr/bin/perl\nuse strict;\nuse warnings;\n\n# you need 3 disposable userids: sam, frodo, gollum.  Then the test user (say"
  },
  {
    "path": "t/mirror-test-rc",
    "chars": 5215,
    "preview": "# This file is in perl syntax.  But you do NOT need to know perl to edit it --\n# just mind the commas, use single quotes"
  },
  {
    "path": "t/mirror-test-setup.sh",
    "chars": 4610,
    "preview": "#!/bin/bash\n\nset -e\nhosts=\"frodo sam gollum\"\nmainhost=frodo\n\n# setup software\nbd=`gitolite query-rc -n GL_BINDIR`\nmkdir "
  },
  {
    "path": "t/mirror-test-ssh-config",
    "chars": 147,
    "preview": "host frodo\n    user frodo\n    hostname localhost\n\nhost sam\n    user sam\n    hostname localhost\n\nhost gollum\n    user gol"
  },
  {
    "path": "t/partial-copy.t",
    "chars": 5765,
    "preview": "#!/usr/bin/perl\nuse strict;\nuse warnings;\n\n# this is hardcoded; change it if needed\nuse lib \"src/lib\";\nuse Gitolite::Tes"
  },
  {
    "path": "t/perm-default-roles.t",
    "chars": 4230,
    "preview": "#!/usr/bin/perl\nuse strict;\nuse warnings;\n\n# this is hardcoded; change it if needed\nuse lib \"src/lib\";\nuse Gitolite::Tes"
  },
  {
    "path": "t/perm-roles.t",
    "chars": 5501,
    "preview": "#!/usr/bin/perl\nuse strict;\nuse warnings;\n\n# this is hardcoded; change it if needed\nuse lib \"src/lib\";\nuse Gitolite::Tes"
  },
  {
    "path": "t/perms-groups.t",
    "chars": 1987,
    "preview": "#!/usr/bin/perl\nuse strict;\nuse warnings;\n\n# this is hardcoded; change it if needed\nuse lib \"src/lib\";\nuse Gitolite::Tes"
  },
  {
    "path": "t/personal-branches.t",
    "chars": 3076,
    "preview": "#!/usr/bin/perl\nuse strict;\nuse warnings;\n\n# this is hardcoded; change it if needed\nuse lib \"src/lib\";\nuse Gitolite::Tes"
  },
  {
    "path": "t/reference.t",
    "chars": 1217,
    "preview": "#!/usr/bin/perl\nuse strict;\nuse warnings;\n\n# this is hardcoded; change it if needed\nuse lib \"src/lib\";\nuse Gitolite::Tes"
  },
  {
    "path": "t/refex-expr-test-1",
    "chars": 1382,
    "preview": "#!/bin/bash\n\n# not part of the official test suite (yet); just some q&d testing\n\n# to be run from ~/gitolite as ./$0\n\nse"
  },
  {
    "path": "t/refex-expr-test-2",
    "chars": 1152,
    "preview": "#!/bin/bash\n\n# not part of the official test suite (yet); just some q&d testing\n\n# to be run from ~/gitolite as ./$0\n\nse"
  },
  {
    "path": "t/refex-expr-test-3",
    "chars": 1052,
    "preview": "#!/bin/bash\n\n# not part of the official test suite (yet); just some q&d testing\n\n# to be run from ~/gitolite as ./$0\n\nse"
  },
  {
    "path": "t/refex-expr-test-9",
    "chars": 2138,
    "preview": "#!/bin/bash\n\n# not part of the official test suite (yet); just some q&d testing\n\n# to be run from ~/gitolite as ./$0\n\nse"
  },
  {
    "path": "t/repo-specific-hooks.t",
    "chars": 8239,
    "preview": "#!/usr/bin/perl\nuse strict;\nuse warnings;\n\n# this is hardcoded; change it if needed\nuse lib \"src/lib\";\nuse Gitolite::Tes"
  },
  {
    "path": "t/reset",
    "chars": 545,
    "preview": "#!/usr/bin/perl\nuse strict;\nuse warnings;\n\nBEGIN {\n    unlink \"$ENV{HOME}/.ssh/authorized_keys\";\n}\n\n# this is hardcoded;"
  },
  {
    "path": "t/rule-seq.t",
    "chars": 1921,
    "preview": "#!/usr/bin/perl\nuse strict;\nuse warnings;\n\n# this is hardcoded; change it if needed\nuse lib \"src/lib\";\nuse Gitolite::Tes"
  },
  {
    "path": "t/sequence.t",
    "chars": 2684,
    "preview": "#!/usr/bin/perl\nuse strict;\nuse warnings;\n\n# this is hardcoded; change it if needed\nuse lib \"src/lib\";\nuse Gitolite::Tes"
  },
  {
    "path": "t/smart-http",
    "chars": 1730,
    "preview": "#!/bin/bash\n\ndie() { echo \"$@\"; exit 1; }\n\n# git clone `url u1 r1`\nurl() {\n    echo http://$1:$1@localhost/git/$2.git\n}\n"
  },
  {
    "path": "t/smart-http.root-setup",
    "chars": 3538,
    "preview": "#!/bin/bash\n\n# ----------------------------------------------------------------------\n# please do not even LOOK at this "
  },
  {
    "path": "t/ssh-authkeys.t",
    "chars": 2670,
    "preview": "#!/usr/bin/perl\nuse strict;\nuse warnings;\n\n# this is hardcoded; change it if needed\nuse lib \"src/lib\";\nuse Gitolite::Tes"
  },
  {
    "path": "t/ssh-basic.t",
    "chars": 1439,
    "preview": "#!/usr/bin/perl\nuse strict;\nuse warnings;\n\n# this is hardcoded; change it if needed\nuse lib \"src/lib\";\nuse Gitolite::Com"
  },
  {
    "path": "t/templates.t",
    "chars": 47409,
    "preview": "#!/usr/bin/perl\nuse strict;\nuse warnings;\nuse 5.10.0;\nuse Data::Dumper;\n\n# this is hardcoded; change it if needed\nuse li"
  },
  {
    "path": "t/vrefs-1.t",
    "chars": 4718,
    "preview": "#!/usr/bin/perl\nuse strict;\nuse warnings;\n\n# this is hardcoded; change it if needed\nuse lib \"src/lib\";\nuse Gitolite::Tes"
  },
  {
    "path": "t/vrefs-2.t",
    "chars": 4548,
    "preview": "#!/usr/bin/perl\nuse strict;\nuse warnings;\n\n# this is hardcoded; change it if needed\nuse lib \"src/lib\";\nuse Gitolite::Tes"
  },
  {
    "path": "t/wild-1.t",
    "chars": 3863,
    "preview": "#!/usr/bin/perl\nuse strict;\nuse warnings;\n\n# this is hardcoded; change it if needed\nuse lib \"src/lib\";\nuse Gitolite::Tes"
  },
  {
    "path": "t/wild-2.t",
    "chars": 3754,
    "preview": "#!/usr/bin/perl\nuse strict;\nuse warnings;\n\n# this is hardcoded; change it if needed\nuse lib \"src/lib\";\nuse Gitolite::Tes"
  },
  {
    "path": "t/writable.t",
    "chars": 3587,
    "preview": "#!/usr/bin/perl\nuse strict;\nuse warnings;\n\n# this is hardcoded; change it if needed\nuse lib \"src/lib\";\nuse Gitolite::Tes"
  },
  {
    "path": "t/z-end.t",
    "chars": 280,
    "preview": "#!/usr/bin/perl\nuse strict;\nuse warnings;\n\n# this is hardcoded; change it if needed\nuse lib \"src/lib\";\nuse Gitolite::Tes"
  }
]

About this extraction

This page contains the full source code of the sitaramc/gitolite GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 186 files (682.3 KB), approximately 214.8k tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!