summaryrefslogtreecommitdiffstats
path: root/src/VREF
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 14:17:27 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 14:17:27 +0000
commitaae1a14ea756102251351d96e2567b4986d30e2b (patch)
treea1af617672e26aee4c1031a3aa83e8ff08f6a0a5 /src/VREF
parentInitial commit. (diff)
downloadgitolite3-upstream.tar.xz
gitolite3-upstream.zip
Adding upstream version 3.6.12.upstream/3.6.12upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/VREF')
-rwxr-xr-xsrc/VREF/COUNT51
-rwxr-xr-xsrc/VREF/EMAIL-CHECK66
-rwxr-xr-xsrc/VREF/FILETYPE45
-rwxr-xr-xsrc/VREF/MAX_NEWBIN_SIZE42
-rw-r--r--src/VREF/MERGE-CHECK49
-rwxr-xr-xsrc/VREF/NAME_NC33
-rwxr-xr-xsrc/VREF/VOTES80
-rwxr-xr-xsrc/VREF/lock36
-rwxr-xr-xsrc/VREF/partial-copy41
-rwxr-xr-xsrc/VREF/refex-expr99
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