summaryrefslogtreecommitdiffstats
path: root/src/lib/Gitolite/Hooks
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/Gitolite/Hooks')
-rw-r--r--src/lib/Gitolite/Hooks/PostUpdate.pm75
-rw-r--r--src/lib/Gitolite/Hooks/Update.pm172
2 files changed, 247 insertions, 0 deletions
diff --git a/src/lib/Gitolite/Hooks/PostUpdate.pm b/src/lib/Gitolite/Hooks/PostUpdate.pm
new file mode 100644
index 0000000..a76d1d9
--- /dev/null
+++ b/src/lib/Gitolite/Hooks/PostUpdate.pm
@@ -0,0 +1,75 @@
+package Gitolite::Hooks::PostUpdate;
+
+# everything to do with the post-update hook
+# ----------------------------------------------------------------------
+
+@EXPORT = qw(
+ post_update
+ post_update_hook
+);
+
+use Exporter 'import';
+
+use Gitolite::Rc;
+use Gitolite::Common;
+
+use strict;
+use warnings;
+
+# ----------------------------------------------------------------------
+
+sub post_update {
+ trace( 3, 'post-up', @ARGV );
+ exit 0 unless grep( m(^refs/heads/master$), @ARGV );
+ # this is the *real* post_update hook for gitolite
+
+ tsh_try("git ls-tree --name-only master");
+ _die "no files/dirs called 'hooks' or 'logs' are allowed" if tsh_text() =~ /^(hooks|logs)$/m;
+
+ my $hooks_changed = 0;
+ {
+ local $ENV{GIT_WORK_TREE} = $rc{GL_ADMIN_BASE};
+
+ tsh_try("git diff --name-only master");
+ $hooks_changed++ if tsh_text() =~ m(/hooks/common/);
+ # the leading slash ensure that this hooks/common directory is below
+ # some top level directory, not *at* the top. That's LOCAL_CODE, and
+ # it's actual name could be anything but it doesn't matter to us.
+
+ tsh_try("git checkout -f --quiet master");
+ }
+ _system("gitolite compile");
+ _system("gitolite setup --hooks-only") if $hooks_changed;
+ _system("gitolite trigger POST_COMPILE");
+
+ exit 0;
+}
+
+{
+ my $text = '';
+
+ sub post_update_hook {
+ if ( not $text ) {
+ local $/ = undef;
+ $text = <DATA>;
+ }
+ return $text;
+ }
+}
+
+1;
+
+__DATA__
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use lib $ENV{GL_LIBDIR};
+use Gitolite::Hooks::PostUpdate;
+
+# gitolite post-update hook (only for the admin repo)
+# ----------------------------------------------------------------------
+
+post_update(); # is not expected to return
+exit 1; # so if it does, something is wrong
diff --git a/src/lib/Gitolite/Hooks/Update.pm b/src/lib/Gitolite/Hooks/Update.pm
new file mode 100644
index 0000000..2bc43a8
--- /dev/null
+++ b/src/lib/Gitolite/Hooks/Update.pm
@@ -0,0 +1,172 @@
+package Gitolite::Hooks::Update;
+
+# everything to do with the update hook
+# ----------------------------------------------------------------------
+
+@EXPORT = qw(
+ update
+ update_hook
+);
+
+use Exporter 'import';
+
+use Gitolite::Rc;
+use Gitolite::Common;
+use Gitolite::Conf::Load;
+
+use strict;
+use warnings;
+
+$|++;
+
+# ----------------------------------------------------------------------
+
+sub update {
+ # this is the *real* update hook for gitolite
+
+ bypass() if $ENV{GL_BYPASS_ACCESS_CHECKS};
+
+ my ( $ref, $oldsha, $newsha, $oldtree, $newtree, $aa ) = args(@ARGV);
+
+ trace( 2, $ENV{GL_REPO}, $ENV{GL_USER}, $aa, @ARGV );
+
+ my $ret = access( $ENV{GL_REPO}, $ENV{GL_USER}, $aa, $ref );
+ trigger( 'ACCESS_2', $ENV{GL_REPO}, $ENV{GL_USER}, $aa, $ref, $ret, $oldsha, $newsha );
+ _die $ret if $ret =~ /DENIED/;
+
+ check_vrefs( $ref, $oldsha, $newsha, $oldtree, $newtree, $aa );
+
+ gl_log( 'update', $ENV{GL_REPO}, $ENV{GL_USER}, $aa, @ARGV, $ret );
+ exit 0;
+}
+
+sub bypass {
+ require Cwd;
+ Cwd->import;
+ gl_log( 'update', getcwd(), '(' . ( $ENV{USER} || '?' ) . ')', 'bypass', @ARGV );
+ exit 0;
+}
+
+sub check_vrefs {
+ my ( $ref, $oldsha, $newsha, $oldtree, $newtree, $aa ) = @_;
+ my $name_seen = 0;
+ my $n_vrefs = 0;
+ for my $vref ( vrefs( $ENV{GL_REPO}, $ENV{GL_USER} ) ) {
+ $n_vrefs++;
+ if ( $vref =~ m(^VREF/NAME/) ) {
+ # this one is special; we process it right here, and only once
+ next if $name_seen++;
+
+ for my $ref ( map { chomp; s(^)(VREF/NAME/); $_; } `git diff --name-only $oldtree $newtree` ) {
+ check_vref( $aa, $ref );
+ }
+ } else {
+ my ( $dummy, $pgm, @args ) = split '/', $vref;
+ $pgm = _which( "VREF/$pgm", 'x' );
+ $pgm or _die "'$vref': helper program missing or unexecutable";
+
+ open( my $fh, "-|", $pgm, @_, $vref, @args ) or _die "'$vref': can't spawn helper program: $!";
+ while (<$fh>) {
+ # print non-vref lines and skip processing (for example,
+ # normal STDOUT by a normal update hook)
+ unless (m(^VREF/)) {
+ print;
+ next;
+ }
+ my ( $ref, $deny_message ) = split( ' ', $_, 2 );
+ check_vref( $aa, $ref, $deny_message );
+ }
+ close($fh) or _die $!
+ ? "Error closing sort pipe: $!"
+ : "$vref: helper program exit status $?";
+ }
+ }
+ return $n_vrefs;
+}
+
+sub check_vref {
+ my ( $aa, $ref, $deny_message ) = @_;
+
+ my $ret = access( $ENV{GL_REPO}, $ENV{GL_USER}, $aa, $ref );
+ trace( 2, "access($ENV{GL_REPO}, $ENV{GL_USER}, $aa, $ref)", "-> $ret" );
+ if ( $ret =~ /by fallthru/ ) {
+ trace( 3, "remember, fallthru is success here!" );
+ return;
+ }
+ trigger( 'ACCESS_2', $ENV{GL_REPO}, $ENV{GL_USER}, $aa, $ref, $ret );
+ _die "$ret" . ( $deny_message ? "\n$deny_message" : '' ) if $ret =~ /DENIED/;
+}
+
+{
+ my $text = '';
+
+ sub update_hook {
+ if ( not $text ) {
+ local $/ = undef;
+ $text = <DATA>;
+ }
+ return $text;
+ }
+}
+
+# ----------------------------------------------------------------------
+
+sub args {
+ my ( $ref, $oldsha, $newsha ) = @_;
+ my ( $oldtree, $newtree, $aa );
+
+ # this is special to git -- the hash of an empty tree
+ my $empty = '4b825dc642cb6eb9a060e54bf8d69288fbee4904';
+ $oldtree = $oldsha eq '0' x 40 ? $empty : $oldsha;
+ $newtree = $newsha eq '0' x 40 ? $empty : $newsha;
+
+ my $merge_base = '0' x 40;
+ # for branch create or delete, merge_base stays at '0'x40
+ chomp( $merge_base = `git merge-base $oldsha $newsha` )
+ unless $oldsha eq '0' x 40
+ or $newsha eq '0' x 40;
+
+ $aa = 'W';
+ # tag rewrite
+ $aa = '+' if $ref =~ m(refs/tags/) and $oldsha ne ( '0' x 40 );
+ # non-ff push to ref (including ref delete)
+ $aa = '+' if $oldsha ne $merge_base;
+
+ $aa = 'D' if ( option( $ENV{GL_REPO}, 'DELETE_IS_D' ) ) and $newsha eq '0' x 40;
+ $aa = 'C' if ( option( $ENV{GL_REPO}, 'CREATE_IS_C' ) ) and $oldsha eq '0' x 40;
+
+ # and now "M" commits. All the other accesses (W, +, C, D) were mutually
+ # exclusive in some sense. Sure a W could be a C or a + could be a D but
+ # that's by design. A merge commit, however, could still be any of the
+ # others (except a "D").
+
+ # so we have to *append* 'M' to $aa (if the repo has MERGE_CHECK in
+ # effect and this push contains a merge inside)
+
+ if ( option( $ENV{GL_REPO}, 'MERGE_CHECK' ) ) {
+ if ( $oldsha eq '0' x 40 or $newsha eq '0' x 40 ) {
+ _warn "ref create/delete ignored for purposes of merge-check\n";
+ } else {
+ $aa .= 'M' if `git rev-list -n 1 --merges $oldsha..$newsha` =~ /./;
+ }
+ }
+
+ return ( $ref, $oldsha, $newsha, $oldtree, $newtree, $aa );
+}
+
+1;
+
+__DATA__
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use lib $ENV{GL_LIBDIR};
+use Gitolite::Hooks::Update;
+
+# gitolite update hook
+# ----------------------------------------------------------------------
+
+update(); # is not expected to return
+exit 1; # so if it does, something is wrong