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