From 7124faa9f3ee06bb095b8eea3cea1caeab3a885f Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Thu, 7 Jan 2010 17:59:34 +0530 Subject: [PATCH] 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. --- conf/example.conf | 34 ++++++++++++++++++++++++++++++++++ doc/3-faq-tips-etc.mkd | 10 ++++++++++ src/gl-compile-conf | 10 +++++----- src/hooks/update | 10 +++++----- 4 files changed, 54 insertions(+), 10 deletions(-) diff --git a/conf/example.conf b/conf/example.conf index 2c3c6fb..a9b037c 100644 --- a/conf/example.conf +++ b/conf/example.conf @@ -165,6 +165,40 @@ repo git # looking for (`W` or `+`), or a minus (`-`), results in success, or 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 # ----------------------- diff --git a/doc/3-faq-tips-etc.mkd b/doc/3-faq-tips-etc.mkd index a7ee05a..808cf03 100644 --- a/doc/3-faq-tips-etc.mkd +++ b/doc/3-faq-tips-etc.mkd @@ -13,6 +13,7 @@ In this document: * differences from gitosis * simpler syntax * two levels of access rights checking + * file/dir NAME based restrictions * error checking the config file * delegating parts of the config file * 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 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 gitosis does not do any. I just found out that if you mis-spell `members` as diff --git a/src/gl-compile-conf b/src/gl-compile-conf index 4c34d1d..23f26c7 100755 --- a/src/gl-compile-conf +++ b/src/gl-compile-conf @@ -200,9 +200,9 @@ sub parse_conf_file # if no ref is given, this PERM applies to all 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/" - @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" @users = expand_list ( @users ) @@ -239,13 +239,13 @@ sub parse_conf_file # for 2nd level check, store each "ref, perms" pair in order 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 # in this commit for why) so we would *very* much like # 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 - $repos{$repo}{PATH_LIMITS} = 1 if $ref =~ /^PATH\//; + $repos{$repo}{NAME_LIMITS} = 1 if $ref =~ /^NAME\//; push @{ $repos{$repo}{$user} }, { $ref => $perms } unless $rurp_seen{$repo}{$user}{$ref}{$perms}++; } diff --git a/src/hooks/update b/src/hooks/update index 235fa40..a3c1be4 100755 --- a/src/hooks/update +++ b/src/hooks/update @@ -65,12 +65,12 @@ push @allowed_refs, @ { $repos{$ENV{GL_REPO}}{'@all'} || [] }; # prepare the list of refs to be checked # 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 -# updated as a potential "ref" and check that, if PATH-based restrictions have +# 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 NAME-based restrictions have # been specified 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 my $empty='4b825dc642cb6eb9a060e54bf8d69288fbee4904'; # 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 my $oldtree = $oldsha eq '0' x 40 ? $empty : $oldsha; 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 = ''; @@ -111,7 +111,7 @@ sub check_ref { # 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), -# 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) my $log_refex = check_ref(shift @refs); check_ref($_) for @refs;