You might recall from the basic administration page that this file is part of the gitolite-admin repo. You need to clone that repo, make and commit changes to this file, and push the commits back.
conf/gitolite.conf file (often called just "the conf file" for short) is one of the two most important files in gitolite. It specifies repo names and access rules, as well as repo options of various kinds and git-config values.
Pretty much all day-to-day management, except managing users, happens from this file.
We'll use the following example to describe it's features. (A tip of the hat to Teemu Matilainen's gitolite vim-syntax file for the colors.)
# sample conf/gitolite.conf file @staff = dilbert alice # groups @projects = foo bar repo @projects baz # repos RW+ = @staff # rules - master = ashok RW = ashok R = wally option deny-rules = 1 # options config hooks.emailprefix = '[%GL_REPO] ' # git-config
As the example above shows, the syntax is fairly straightforward and simple.
-. Usernames can optionally be followed by an
@and a domainname containing at least one
.(this allows you to use an email address as someone's username).
/characters (this allows you to put your repos in a tree-structure for convenience).
Gitolite allows you to break up the configuration into multiple files and include them in the main file for convenience. For example:
will include the contents of the file "conf/foo.conf".
You can also use a glob (
include "*.conf"), or put your include files into subdirectories of "conf" (
include "foo/bar.conf"), or both (
Included files are always searched from the gitolite-admin repo's "conf/" directory, unless you supplied an absolute path. (Note: in the interests of cloning the admin-repo sanely you should avoid absolute paths!)
If you ended up recursing, files that have been already processed once are skipped, with a warning.
It is not a fatal error for an include file to be missing. If it is a glob, you won't even get a warning.
subconf, a command that is very closely related to
include, is documented here.
Please note that whenever you see "the
conf/gitolite.conf file" or "the conf file" in gitolite documentation, it means the combined text after the include processing is done.
You can group repos or users for convenience. The syntax is the same for both and does not distinguish; until you use the group name it could really be either.
Here's an example:
@developers = dilbert alice wally
Group definitions accumulate; this is the same as the above:
@developers = dilbert @developers = alice @developers = wally
You can use one group in another group definition; the values will be expanded immediately (meaning later additions will not appear in the second group).
@developers = dilbert alice @interns = ashok @staff = @interns @developers @developers = wally # wally is NOT part of @staff
Here's a very simple but complete example of using groups:
@developers = dilbert alice wally @foss-repos = git gitolite repo @foss-repos RW+ = @developers
@all is a special group name that is often convenient to use if you really mean "all repos" or "all users".
repo foo RW = @foo @foo = u1 u2
Gitolite cannot truly catch undefined groups because the conf parser is 1-pass, and you're allowed to define a group after it is used, like so:
(v3.5.3+) However, in a simplistic attempt to help people tearing their hair out because of a typo, gitolite will warn if a group is not defined when it is used. So if you defined it later, either ignore the warning or move the definition up.
Note that these warnings do NOT appear if you're getting user group info from LDAP.
Gitolite's groups are pretty convenient, but some organisations already have similar (or sufficient) information in their LDAP store.
Gitolite can tap into that information, with a little help. Write a program which, given a username, queries your LDAP store and returns a space-separated list of groups that the user is a member of. Then put the full path to this program in an rc variable called
GROUPLIST_PGM, like so:
GROUPLIST_PGM => '/home/git/bin/ldap-query-groups',
Now you can use those groupnames in access rules in gitolite, because the user is a member of those groups as well as any normal gitolite groups you may have added him to in the conf file.
Caution: your program must do its own logging if you want the audit trail of "why/how did this user get access to this repo at this time?" to resolve properly. Gitolite does not do any logging of the results of the queries because for people who don't need it that would be a huge waste.
Some of the pictures are thanks (enormous thanks!) to someone who contributed them but does not want to be named (go figure!). She even converted them to ditaa format when I asked; these are not as pretty as what she sent me originally but they're vim-editable in source form :-)
This section talks about how gitolite's access rules work. It's a very important section, and well worth spending some time on.
Gitolite's access rules are designed to be easy to use for common situations, such as some of the examples you saw earlier. However, they also pack a lot of power and flexibility.
Access rules decide whether a particular access is allowed or denied. An access is defined by four pieces of data: "reponame, username, operation, and ref". Each rule also has four similar pieces of data, and of course there are several rules -- some people have thousands! -- in the conf file. This section will try and explain how these rules are used to decide if a given operation is to be allowed or denied.
repo foo bar RW+ = alice @teamleads - master = dilbert @devteam - refs/tags/v[0-9] = dilbert @devteam RW+ dev/ = dilbert @devteam RW = dilbert @devteam R = @managers
You've seen some simple rules so far, for example in the basic administration page. Here's a slightly more complex one, just for illustration.
A "repo" line names one or more repos, and is followed by one or more rule lines. All the rules from then till the next "repo" line apply to the repo(s) specified in the repo line -- in this example, the 'foo' and 'bar' repos.
Each rule line has a "permission" field, zero or more "refex"es, and one or more user or user group name after the equal sign.
The "repo" line can also have repo groups, as we have seen in the section on groups above. Also, it can have regular expressions that match multiple repos.
Before describing the various fields more formally, here's a description of what this specific rule list is saying:
alice and the team leads can do whatever they want (i.e., push, rewind, or delete any branch or tag).
managers can read the repo but they can't push anything.
More formally, a rule line has the following fields:
The permission field gives the type of access this rule line permits. The most commonly used permissions are:
-(the minus sign), to deny access.
There are also other, less commonly used, types of permissions.
You cannot write rules for all possible branch and tag names (i.e., refs) that users will push. The only sensible way to do this is to use regular expressions instead.
A refex is a word I made up to mean "a regex that matches a ref".
If no refex is supplied, it defaults to
refs/.*, for example in a rule like this:
RW = alice
A refex not starting with
VREF/) is assumed to start with
refs/heads/. This means normal branches can be conveniently written like this:
RW master = alice # becomes 'refs/heads/master' internally
while tags will need to be fully qualified
RW refs/tags/v[0-9] = bob
A refex is implicitly anchored at the start, but not at the end. In regular expression lingo, a
^ is assumed at the start (but no
$ at the end is assumed). So a refex of
master will match all these refs:
refs/heads/master refs/heads/master1 refs/heads/master2 refs/heads/master/full
More commonly, a refex of
refs/tags/v[0-9] will match all versioned tags (i.e., not just
v1 but also
If you want to restrict the match to just the one specific ref, add a $ at the end, for example:
RW master$ = alice
You can also use virtual refs to perform extra checks and controls that you can't do with just the normal ref (like refs/heads/master) being pushed. The most common example is restricting pushes by dir/file name, but there are lots of other possibilities.
Like the repos on the repo line, you can have any number of user names and/or user group names on the rule line. (However, please note that there is no concept of regular expressions for user names).
SECTION SUMMARY: at this point you know that each rule basically specifies a repo, user, permission, and a "refex".
All the rules for a repo need not be specified in one place. For example, you might see something like this, perhaps at the top or bottom of the conf file:
# managers should be able to read any repo repo @all R = @managers
or perhaps this.
clearly, both these constructs affect repos which may have their own rules elsewhere.
# anyone can clone open source repos repo @FOSS R = @all
If a bunch of projects share some naming convention, you can specify any rules that are common to all of them by specifying the set of repos that are affected as a regular expression. Notice that the regex can be defined directly in the repo line, or it can be defined in a group and the group used in the repo line; it's all the same to gitolite.
repo FOSS/..* # ...rules for repos under FOSS/ @secret = secret/..* private/..* repo @secret # ...rules for repos under secret/ and private/
Finally, although not very common, you can certainly do things like this. Note that the "other repos and rules" may indirectly include repo "foo" (for example it there were rules for "repo @all", or some other group that "foo" was a member of).
repo foo # ...some rules... # ...other repos and rules... repo foo # ...more rules for foo...
When access is being checked for an operation against a repo, all the rules that pertain to that repo are collected, in the order they were seen in the conf file.
Gitolite then discards those rules that do not apply to the user whose access is being checked. (A rule applies to a user if the user's name appears in the rule (after the equal sign), or if he is a member of any of the group names that appear in the rule.)
To be very specific, when we speak of "user" and "repo" in rules,
Access control rule matching is pretty simple. From the previous section, you know what "permission", "refex", "user", and "repo" are. Here's how the rules are used to decide whether to allow or deny a request.
Access is checked once only for "read" operations, but twice for "write"s.
(Note that the
deny-rules option, mentioned below, is explained in more detail in a later section.)
Check #1: the first check happens as soon as gitolite-shell receives control (from sshd or httpd). gitolite-shell will pass control to git-upload-pack or git-receive-pack only if this check succeeds.
deny-rulesoption is not in effect for this repo, discard all the
-, access is denied
R, access is allowed
W, access is allowed
Check #2: the second check only happens for "push" operations. It is invoked by
git-receive-pack running the gitolite-installed
update hook. If access is denied, the update hook fails, and git then aborts the push for this ref. (See
man githooks for more.)
In the following description, we use the word
operation instead of
W, because the actual operation could be a plain, fast-forward, push (
W) or a rewind/delete (
+). Other, less commonly used, values are "C", "D", or "M"; see here.
-, access is denied
Just to be clear, let's work out an example of what happens when dilbert tries to push a branch called "xyz".
We'll pretend the rule list looks like this.
# managers should be able to read any repo repo @all R = @managers # ...other rules for other repos... repo foo bar RW+ = alice @teamleads - master = dilbert @devteam - refs/tags/v[0-9] = dilbert @devteam RW+ dev/ = dilbert @devteam RW = dilbert @devteam R = @managers
After adding a default refex and expanding the supplied ones (see the refex section earlier), this is what it looks like. We've added line numbers for convenience; we'll see why later.
1 # managers should be able to read any repo 2 repo @all 3 R refs/.* = @managers 4 5 # ...other rules for other repos... 6 7 repo foo bar 8 9 RW+ refs/.* = alice @teamleads 10 - refs/heads/master = dilbert @devteam 11 - refs/tags/v[0-9] = dilbert @devteam 12 RW+ refs/heads/dev/ = dilbert @devteam 13 RW refs/.* = dilbert @devteam 14 R refs/.* = @managers
This represents a set of rules that are basically this:
repo user perm ref (from line) foo @managers R refs/.\* 3 foo alice RW+ refs/.\* 9 foo @teamleads RW+ refs/.\* 9 foo dilbert - refs/heads/master 10 foo @devteam - refs/heads/master 10 foo dilbert - refs/tags/v[0-9] 11 foo @devteam - refs/tags/v[0-9] 11 foo dilbert RW+ refs/heads/dev/ 12 foo @devteam RW+ refs/heads/dev/ 12 foo dilbert RW refs/.\* 13 foo @devteam RW refs/.\* 13 foo @managers R refs/.\* 14
Which of these rules apply for dilbert? We'll assume he's not a team lead, as that would defeat the whole purpose of this example! We know he's not a manager, as that would defeat the whole purpose of the comic! Finally, we assume he's also not part of "@devteam", (otherwise why would you name him separately in all those lines?).
So we discard all those rules, which leaves us, for repo "foo" and user "dilbert":
perm ref (from line) - refs/heads/master 10 - refs/tags/v[0-9] 11 RW+ refs/heads/dev/ 12 RW refs/.\* 13
So what happens when dilbert tries to push a branch called "xyz"?
At check #1, the data gitolite has is that "oper" is "W" (and ref of course is unknown). We discard lines 10 and 11 (the
deny-rules option is off by default, so we ignore
- rules). Line 12 supplies a perm of "RW+", which contains "W" (the "oper") so access is allowed.
At check #2, the data gitolite has is that "oper" is "W" and ref is
refs/heads/xyz. We discard the first three rules, since the ref does not match any of those refexes. That leaves just line 13.
If the push were a fast-forward push, the "oper" would be "W", and since it is contained in the perm for rule 13, access is allowed.
However, if he were to try a rewind-push, then the "oper" would be "+", which is not contained in "RW", it wouldn't match, then control would go back for the next rule, and since there aren't any more, access would be denied.
(v3.6.1) Gitolite can help you trace this logic quickly and easily. Here's one example run, with the above rules. This one tests whether dilbert can push to repo foo (check #1). Note that the syntax for specifying an unknown ref in this command is 'any'.
Now see what happens when we try check #2 (we've omitted the legend in the output, since it's always the same):
And if you try a force push:
I hope that was useful! Be sure you correlated the output of 'gitolite access -s' with the rule workflow pictures and corresponding descriptions to cement your understanding.
Note: This section has one or more forward references, referring to gitolite options, and the special users gitweb and daemon).
Normally, deny rules are ignored by access check #1 (the one that runs before git-upload-pack or git-receive-pack is called by gitolite-shell); they apply only to check #2 (the update hook check).
But sometimes you want this "pre-git" access check to respect deny rules; i.e., use the flow of check #2, not check #1. You tell gitolite to do this by setting the "deny-rules" option for the repo; when you do that, the flow of check #2 is used for both stages, before git and in the update hook.
Here's an example. Here, we have lots of repos, which should all be accessible by gitweb or daemon, so we want the convenience provided by lines 6 and 7 (we don't want to put line 7 in each repo). However, we also have some secret repos (maybe the gitolite-admin repo and some others that we will list), which we want to prevent gitweb or daemon from seeing.
How do we do that?
1 @secret = gitolite-admin secret-repo/..* 2 repo @secret 3 - = gitweb daemon 4 5 6 repo @all 7 R = gitweb daemon 8 9 # ...other repos and rules... 10
The naive approach -- putting in a deny rule just for those repos -- doesn't work. In fact nothing else seems to work either; you'll have to replace the
1 @secret = gitolite-admin secret-repo/..* 2 repo @secret 3 - = gitweb daemon 4 option deny-rules = 1 5 6 repo @all 7 R = gitweb daemon 8 9 # ...other repos and rules... 10
What you really want is for that repo to always use check #2, even when it doesn't actually have a ref to test for.
This is done by adding one line, line 4 in this example. This sets a gitolite "option" that says you want "deny rules" to be applicable even for read access.
Once you do that, all you need to do is to ensure that the first rule encountered by these two "users" for those repos is a deny rule, so that it can take effect first. In this example, the placement of lines 2, 3 vis-a-vis lines 6, 7 matters -- don't switch them!
@open = git gitolite foss/..* [...] repo @all - = gitweb daemon option deny-rules = 1 repo @open R = gitweb daemon option deny-rules = 0
In this example the "open" repos are fewer in number, so it is the opposite situation to the above in terms of our ability to enumerate all the repos.
To see why this works, you need to understand that for options and config lines, a later setting overrides earlier ones. So we set it to 1 for all repos, then selectively set it to 0 for some.
This means the "deny-rules" option applies to all the repos except the "open" repos, and so the first rule encountered by gitweb and daemon is a deny rule, so they are denied read access. The "open" repos, on the other hand, get the normal default behaviour, which is to ignore deny rules for read access, and thus they only see the "R" permission.
Git supplies enough information to the update hook to be able to distinguish several types of writes.
The most common are:
RW-- create a ref or fast-forward push a ref. No rewinds or deletes.
RW+-- create, fast-forward push, rewind push, or delete a ref.
Sometimes you want to allow people to push, but not create a ref. Or rewind, but not delete a ref. The
D qualifiers help here.
If a rule specifies
RW+C, then rules that do NOT have the C qualifier will no longer permit creating a ref.
Please do not confuse this with the standalone
C permission that allows someone to create a repo
If a rule specifies
RW+D, then rules that do NOT have the D qualifier will no longer permit deleting a ref.
Note: These two can be combined, so you can have
RW+CD as well.
One very rare need is to reject merge commits (a commit series that is not a straight line of commits). The
M qualifier helps here:
Mappended to the permissions, rules that do NOT have it will reject a commit sequence that contains a merge commit (i.e., they only accept a straight line series of commits).
The full set of permissions, in regex syntax, is
-|R|RW+?C?D?M?. This expands to one of
RW+CD, all but the first two optionally followed by an
Here's lots more detail on the access check process, with flow diagrams.
When do the access checks happen and what are the four pieces of data (repo, user, operation, ref) in each case?
In these pictures the access checks are marked in yellow.
The picture on the left is for a read (git clone, fetch, ls-remote). There is only one access check for a read operation. If access is denied, the operation aborts. Otherwise, gitolite-shell invokes git-upload-pack.
Notice the information available to the access check. The "oper" (operation) is "R", indicating a read operation. The "ref" is listed as "unknown", although we could also call it "irrelevant"!
Access check #1 proceeds with those 4 bits of information, and either passes or fails. If it passes, gitolite passes control to "git-upload-pack" and its job is done.
The flow for a push operation (the picture on the right) is very similar upto the first access check. The "oper" is "W" now, although the "ref" is still unknown. Even though this is a push, at this stage in the protocol nothing on the server knows what branch or tag or combination of them are coming down the wire, since we haven't executed git-receive-pack yet!
If it succeeds, gitolite passes control to "git-receive-pack", but its job is not done yet. Git will eventually invoke the update hook (see 'man githooks'). Gitolite has already grabbed this hook, which receives from git the ref name being pushed, as well as enough information to compute whether this push is a "fast-forward push" or a "rewind push". Based on this, gitolite sets the "oper" field to "W" or "+", respectively.
Access check #2 proceeds with this information. The result is sent back to git-receive-pack (in the form of an exit code; again, see 'man githooks'), and the push fails or succeeds based on that.
At this point, we have the following pieces of information:
A set of rules, each containing 4 pieces of data: repo, user, perm, refex. They are in the sequence they were found in the conf file.
We discard all rules that do not apply to this repo and this user, which means our set of rules have only two fields: perm, refex.
As a quick reminder, perm is one of R, RW, RW+, or
Four elements that make up the access being attempted: repo, user, oper, ref.
Again, as a reminder, the "oper" is one letter. For "check #1" it is either R or W, and for check #2 it can be W or +.
Note on permissions and "oper": there are other types of permissions, but for our discussion these are enough. The others are rare, and anyway it is easy to extrapolate to them.
With that background, here's the flow. The one on the left is for check #1 (ref is unknown) while the one on the right is for check #2 (ref is known). Note that the yellow (decision) boxes are numbered to help later discussion.
As you can see, deny rules are ignored by check #1 -- they're not tested in any way. For check #2, if there is a deny rule whose refex matched the ref, access is denied (as you'd expect).