diff options
Diffstat (limited to 'src/VREF')
-rwxr-xr-x | src/VREF/COUNT | 51 | ||||
-rwxr-xr-x | src/VREF/EMAIL-CHECK | 66 | ||||
-rwxr-xr-x | src/VREF/FILETYPE | 45 | ||||
-rwxr-xr-x | src/VREF/MAX_NEWBIN_SIZE | 42 | ||||
-rw-r--r-- | src/VREF/MERGE-CHECK | 49 | ||||
-rwxr-xr-x | src/VREF/NAME_NC | 33 | ||||
-rwxr-xr-x | src/VREF/VOTES | 80 | ||||
-rwxr-xr-x | src/VREF/lock | 36 | ||||
-rwxr-xr-x | src/VREF/partial-copy | 41 | ||||
-rwxr-xr-x | src/VREF/refex-expr | 99 |
10 files changed, 542 insertions, 0 deletions
diff --git a/src/VREF/COUNT b/src/VREF/COUNT new file mode 100755 index 0000000..f4c3eae --- /dev/null +++ b/src/VREF/COUNT @@ -0,0 +1,51 @@ +#!/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 diff --git a/src/VREF/EMAIL-CHECK b/src/VREF/EMAIL-CHECK new file mode 100755 index 0000000..34c66f5 --- /dev/null +++ b/src/VREF/EMAIL-CHECK @@ -0,0 +1,66 @@ +#!/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 + diff --git a/src/VREF/FILETYPE b/src/VREF/FILETYPE new file mode 100755 index 0000000..3f1d5f9 --- /dev/null +++ b/src/VREF/FILETYPE @@ -0,0 +1,45 @@ +#!/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 +} diff --git a/src/VREF/MAX_NEWBIN_SIZE b/src/VREF/MAX_NEWBIN_SIZE new file mode 100755 index 0000000..99d51d3 --- /dev/null +++ b/src/VREF/MAX_NEWBIN_SIZE @@ -0,0 +1,42 @@ +#!/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 diff --git a/src/VREF/MERGE-CHECK b/src/VREF/MERGE-CHECK new file mode 100644 index 0000000..a70fe23 --- /dev/null +++ b/src/VREF/MERGE-CHECK @@ -0,0 +1,49 @@ +#!/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; diff --git a/src/VREF/NAME_NC b/src/VREF/NAME_NC new file mode 100755 index 0000000..1a81714 --- /dev/null +++ b/src/VREF/NAME_NC @@ -0,0 +1,33 @@ +#!/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! diff --git a/src/VREF/VOTES b/src/VREF/VOTES new file mode 100755 index 0000000..8dc3563 --- /dev/null +++ b/src/VREF/VOTES @@ -0,0 +1,80 @@ +#!/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 diff --git a/src/VREF/lock b/src/VREF/lock new file mode 100755 index 0000000..0fc7681 --- /dev/null +++ b/src/VREF/lock @@ -0,0 +1,36 @@ +#!/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 diff --git a/src/VREF/partial-copy b/src/VREF/partial-copy new file mode 100755 index 0000000..55a7dcf --- /dev/null +++ b/src/VREF/partial-copy @@ -0,0 +1,41 @@ +#!/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 diff --git a/src/VREF/refex-expr b/src/VREF/refex-expr new file mode 100755 index 0000000..b788dd9 --- /dev/null +++ b/src/VREF/refex-expr @@ -0,0 +1,99 @@ +#!/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 |