NAME-based restrictions

Gitolite allows you to restrict changes by file/dir name.  The syntax
for this used "PATH/" as a prefix to denote such file/dir patterns.
This has now been changed to "NAME/" because PATH is potentially
confusing.

While this is technically a backward-incompatible change, the feature
itself was hitherto undocumented, and only a few people were using it,
so I guess it's not that bad...

Also added documentation now.
This commit is contained in:
Sitaram Chamarty 2010-01-07 17:59:34 +05:30
parent ab3c861241
commit 7124faa9f3
4 changed files with 54 additions and 10 deletions

View file

@ -165,6 +165,40 @@ repo git
# looking for (`W` or `+`), or a minus (`-`), results in success, or failure, # looking for (`W` or `+`), or a minus (`-`), results in success, or failure,
# respectively. A fallthrough also results in failure # respectively. A fallthrough also results in failure
# FILE/DIR NAME BASED RESTRICTIONS
# --------------------------------
# Here's a hopefully self-explanatory example. Assume the project has the
# following contents at the top level: a README, a "doc/" directory, and an
# "src/" directory.
repo foo
RW+ = lead_dev # rule 1
RW = dev1 dev2 dev3 dev4 # rule 2
RW NAME/ = lead_dev # rule 3
RW NAME/doc/ = dev1 dev2 # rule 4
RW NAME/src/ = dev1 dev2 dev3 dev4 # rule 5
# Notes
# - the "NAME/" is part of the syntax; think of it as a keyword if you like
# - file/dir NAME-based restrictions are *in addition* to normal (branch-name
# based) restrictions; they are not a *replacement* for them. This is why
# rule #2 (or something like it, maybe with a more specific branch-name) is
# needed; without it, dev1/2/3/4 cannot push any branches.
# - if a repo has *any* NAME/ rules, then NAME-based restrictions are checked
# for *all* users. This is why rule 3 is needed, even though we don't
# actually have any NAME-based restrictions on lead_dev. Notice the pattern
# on rule 3.
# - *each* file touched by the commits being pushed is checked against those
# rules. So, lead_dev can push changes to any files, dev1/2 can push
# changes to files in "doc/" and "src/" (but not the top level README), and
# dev3/4 can only push changes to files in "src/".
# GITWEB AND DAEMON STUFF # GITWEB AND DAEMON STUFF
# ----------------------- # -----------------------

View file

@ -13,6 +13,7 @@ In this document:
* differences from gitosis * differences from gitosis
* simpler syntax * simpler syntax
* two levels of access rights checking * two levels of access rights checking
* file/dir NAME based restrictions
* error checking the config file * error checking the config file
* delegating parts of the config file * delegating parts of the config file
* easier to specify gitweb "description" and gitweb/daemon access * easier to specify gitweb "description" and gitweb/daemon access
@ -235,6 +236,15 @@ any of the refexes match, the push succeeds. If none of them match, it fails.
Gitolite also allows "exclude" or "deny" rules. See later in this document Gitolite also allows "exclude" or "deny" rules. See later in this document
for details. for details.
#### file/dir NAME based restrictions
In addition to branch-name based restrictions, gitolite also allows you to
restrict what files or directories can be involved in changes being pushed.
This basically uses `git diff --name-only` to obtain the list of files being
changed, treating each filename as a "ref" to be matched.
Please see `conf/example.conf` for syntax and examples.
#### error checking the config file #### error checking the config file
gitosis does not do any. I just found out that if you mis-spell `members` as gitosis does not do any. I just found out that if you mis-spell `members` as

View file

@ -200,9 +200,9 @@ sub parse_conf_file
# if no ref is given, this PERM applies to all refs # if no ref is given, this PERM applies to all refs
@refs = qw(refs/.*) unless @refs; @refs = qw(refs/.*) unless @refs;
# fully qualify refs that dont start with "refs/" or "PATH/"; # fully qualify refs that dont start with "refs/" or "NAME/";
# prefix them with "refs/heads/" # prefix them with "refs/heads/"
@refs = map { m(^(refs|PATH)/) or s(^)(refs/heads/); $_ } @refs; @refs = map { m(^(refs|NAME)/) or s(^)(refs/heads/); $_ } @refs;
# expand the user list, unless it is just "@all" # expand the user list, unless it is just "@all"
@users = expand_list ( @users ) @users = expand_list ( @users )
@ -239,13 +239,13 @@ sub parse_conf_file
# for 2nd level check, store each "ref, perms" pair in order # for 2nd level check, store each "ref, perms" pair in order
for my $ref (@refs) for my $ref (@refs)
{ {
# checking PATH based restrictions is expensive for # checking NAME based restrictions is expensive for
# the update hook (see the changes to src/hooks/update # the update hook (see the changes to src/hooks/update
# in this commit for why) so we would *very* much like # in this commit for why) so we would *very* much like
# to avoid doing it for the large majority of repos # to avoid doing it for the large majority of repos
# that do *not* use PATH limits. Setting a flag that # that do *not* use NAME limits. Setting a flag that
# can be checked right away will help us do that # can be checked right away will help us do that
$repos{$repo}{PATH_LIMITS} = 1 if $ref =~ /^PATH\//; $repos{$repo}{NAME_LIMITS} = 1 if $ref =~ /^NAME\//;
push @{ $repos{$repo}{$user} }, { $ref => $perms } push @{ $repos{$repo}{$user} }, { $ref => $perms }
unless $rurp_seen{$repo}{$user}{$ref}{$perms}++; unless $rurp_seen{$repo}{$user}{$ref}{$perms}++;
} }

View file

@ -65,12 +65,12 @@ push @allowed_refs, @ { $repos{$ENV{GL_REPO}}{'@all'} || [] };
# prepare the list of refs to be checked # prepare the list of refs to be checked
# previously, we just checked $ref -- the ref being updated, which is passed # previously, we just checked $ref -- the ref being updated, which is passed
# to us by git (see man githooks). Now we also have to treat each PATH being # to us by git (see man githooks). Now we also have to treat each NAME being
# updated as a potential "ref" and check that, if PATH-based restrictions have # updated as a potential "ref" and check that, if NAME-based restrictions have
# been specified # been specified
my @refs = ($ref); # the first ref to check is the real one my @refs = ($ref); # the first ref to check is the real one
if (exists $repos{$ENV{GL_REPO}}{PATH_LIMITS}) { if (exists $repos{$ENV{GL_REPO}}{NAME_LIMITS}) {
# this is special to git -- the hash of an empty tree # this is special to git -- the hash of an empty tree
my $empty='4b825dc642cb6eb9a060e54bf8d69288fbee4904'; my $empty='4b825dc642cb6eb9a060e54bf8d69288fbee4904';
# well they're not really "trees" but $empty is indeed the empty tree so # well they're not really "trees" but $empty is indeed the empty tree so
@ -78,7 +78,7 @@ if (exists $repos{$ENV{GL_REPO}}{PATH_LIMITS}) {
# diff' only wants trees # diff' only wants trees
my $oldtree = $oldsha eq '0' x 40 ? $empty : $oldsha; my $oldtree = $oldsha eq '0' x 40 ? $empty : $oldsha;
my $newtree = $newsha eq '0' x 40 ? $empty : $newsha; my $newtree = $newsha eq '0' x 40 ? $empty : $newsha;
push @refs, map { chomp; s/^/PATH\//; $_; } `git diff --name-only $oldtree $newtree`; push @refs, map { chomp; s/^/NAME\//; $_; } `git diff --name-only $oldtree $newtree`;
} }
my $refex = ''; my $refex = '';
@ -111,7 +111,7 @@ sub check_ref {
# and in this version, we have many "refs" to check. The one we print in the # and in this version, we have many "refs" to check. The one we print in the
# log is the *first* one (which is a *real* ref, like refs/heads/master), # log is the *first* one (which is a *real* ref, like refs/heads/master),
# while all the rest (if they exist) are like PATH/something. So we do the # while all the rest (if they exist) are like NAME/something. So we do the
# first one separately to capture it, then run the rest (if any) # first one separately to capture it, then run the rest (if any)
my $log_refex = check_ref(shift @refs); my $log_refex = check_ref(shift @refs);
check_ref($_) for @refs; check_ref($_) for @refs;