1
package Gitolite::Hooks::Update;
3
# everything to do with the update hook
4
# ----------------------------------------------------------------------
11
use Exporter 'import';
15
use Gitolite::Conf::Load;
20
# ----------------------------------------------------------------------
23
# this is the *real* update hook for gitolite
25
bypass() if $ENV{GL_BYPASS_ACCESS_CHECKS};
27
my ( $ref, $oldsha, $newsha, $oldtree, $newtree, $aa ) = args(@ARGV);
29
trace( 1, 'update', $ENV{GL_REPO}, $ENV{GL_USER}, $aa, @ARGV );
31
my $ret = access( $ENV{GL_REPO}, $ENV{GL_USER}, $aa, $ref );
32
trigger( 'ACCESS_2', $ENV{GL_REPO}, $ENV{GL_USER}, $aa, $ref, $ret, $oldsha, $newsha );
33
_die $ret if $ret =~ /DENIED/;
35
check_vrefs( $ref, $oldsha, $newsha, $oldtree, $newtree, $aa );
37
trace( 1, "-> $ret" );
38
gl_log( 'update', $ENV{GL_REPO}, $ENV{GL_USER}, $aa, @ARGV );
45
gl_log( 'update', getcwd(), '(' . ( $ENV{USER} || '?' ) . ')', 'bypass', @ARGV );
50
my ( $ref, $oldsha, $newsha, $oldtree, $newtree, $aa ) = @_;
53
for my $vref ( vrefs( $ENV{GL_REPO}, $ENV{GL_USER} ) ) {
55
if ( $vref =~ m(^VREF/NAME/) ) {
56
# this one is special; we process it right here, and only once
59
for my $ref ( map { chomp; s(^)(VREF/NAME/); $_; } `git diff --name-only $oldtree $newtree` ) {
60
check_vref( $aa, $ref );
63
my ( $dummy, $pgm, @args ) = split '/', $vref;
64
$pgm = _which("VREF/$pgm", 'x');
65
$pgm or _die "'$vref': helper program missing or unexecutable";
67
open( my $fh, "-|", $pgm, @_, $vref, @args ) or _die "'$vref': can't spawn helper program: $!";
69
# print non-vref lines and skip processing (for example,
70
# normal STDOUT by a normal update hook)
75
my ( $ref, $deny_message ) = split( ' ', $_, 2 );
76
check_vref( $aa, $ref, $deny_message );
79
? "Error closing sort pipe: $!"
80
: "$vref: helper program exit status $?";
87
my ( $aa, $ref, $deny_message ) = @_;
89
my $ret = access( $ENV{GL_REPO}, $ENV{GL_USER}, $aa, $ref );
90
trace( 2, "access($ENV{GL_REPO}, $ENV{GL_USER}, $aa, $ref)", "-> $ret" );
91
trigger( 'ACCESS_2', $ENV{GL_REPO}, $ENV{GL_USER}, $aa, $ref, $ret );
92
_die "$ret" . ( $deny_message ? "\n$deny_message" : '' )
93
if $ret =~ /DENIED/ and $ret !~ /by fallthru/;
94
trace( 2, "remember, fallthru is success here!" ) if $ret =~ /by fallthru/;
109
# ----------------------------------------------------------------------
112
my ( $ref, $oldsha, $newsha ) = @_;
113
my ( $oldtree, $newtree, $aa );
115
# this is special to git -- the hash of an empty tree
116
my $empty = '4b825dc642cb6eb9a060e54bf8d69288fbee4904';
117
$oldtree = $oldsha eq '0' x 40 ? $empty : $oldsha;
118
$newtree = $newsha eq '0' x 40 ? $empty : $newsha;
120
my $merge_base = '0' x 40;
121
# for branch create or delete, merge_base stays at '0'x40
122
chomp( $merge_base = `git merge-base $oldsha $newsha` )
123
unless $oldsha eq '0' x 40
124
or $newsha eq '0' x 40;
128
$aa = '+' if $ref =~ m(refs/tags/) and $oldsha ne ( '0' x 40 );
129
# non-ff push to ref (including ref delete)
130
$aa = '+' if $oldsha ne $merge_base;
132
$aa = 'D' if ( option( $ENV{GL_REPO}, 'DELETE_IS_D' ) ) and $newsha eq '0' x 40;
133
$aa = 'C' if ( option( $ENV{GL_REPO}, 'CREATE_IS_C' ) ) and $oldsha eq '0' x 40;
135
# and now "M" commits. All the other accesses (W, +, C, D) were mutually
136
# exclusive in some sense. Sure a W could be a C or a + could be a D but
137
# that's by design. A merge commit, however, could still be any of the
138
# others (except a "D").
140
# so we have to *append* 'M' to $aa (if the repo has MERGE_CHECK in
141
# effect and this push contains a merge inside)
143
if ( option( $ENV{GL_REPO}, 'MERGE_CHECK' ) ) {
144
if ( $oldsha eq '0' x 40 or $newsha eq '0' x 40 ) {
145
_warn "ref create/delete ignored for purposes of merge-check\n";
147
$aa .= 'M' if `git rev-list -n 1 --merges $oldsha..$newsha` =~ /./;
151
return ( $ref, $oldsha, $newsha, $oldtree, $newtree, $aa );
162
use lib $ENV{GL_LIBDIR};
163
use Gitolite::Hooks::Update;
165
# gitolite update hook
166
# ----------------------------------------------------------------------
168
update(); # is not expected to return
169
exit 1; # so if it does, something is wrong