summaryrefslogtreecommitdiffstats
path: root/src/lib/Gitolite/Triggers/Mirroring.pm
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/Gitolite/Triggers/Mirroring.pm')
-rw-r--r--src/lib/Gitolite/Triggers/Mirroring.pm256
1 files changed, 256 insertions, 0 deletions
diff --git a/src/lib/Gitolite/Triggers/Mirroring.pm b/src/lib/Gitolite/Triggers/Mirroring.pm
new file mode 100644
index 0000000..07b7f96
--- /dev/null
+++ b/src/lib/Gitolite/Triggers/Mirroring.pm
@@ -0,0 +1,256 @@
+package Gitolite::Triggers::Mirroring;
+
+use Gitolite::Rc;
+use Gitolite::Common;
+use Gitolite::Conf::Load;
+
+use strict;
+use warnings;
+
+my $hn = $rc{HOSTNAME};
+
+my ( $mode, $master, %copies, %trusted_copies );
+
+# ----------------------------------------------------------------------
+
+sub input {
+ unless ( $ARGV[0] =~ /^server-(\S+)$/ ) {
+ _die "'$ARGV[0]' is not a valid server name" if $ENV{SSH_ORIGINAL_COMMAND} =~ /^USER=(\S+) SOC=(git-receive-pack '(\S+)')$/;
+ return;
+ }
+
+ # note: we treat %rc as our own internal "poor man's %ENV"
+ $rc{FROM_SERVER} = $1;
+ trace( 3, "from_server: $1" );
+ my $sender = $rc{FROM_SERVER} || '';
+
+ # custom peer-to-peer commands. At present the only one is 'perms -c',
+ # sent from a mirror command
+ if ( $ENV{SSH_ORIGINAL_COMMAND} =~ /^CREATOR=(\S+) perms -c '(\S+)'$/ ) {
+ $ENV{GL_USER} = $1;
+
+ my $repo = $2;
+ details($repo);
+ _die "$hn: '$repo' is local" if $mode eq 'local';
+ _die "$hn: '$repo' is native" if $mode eq 'master';
+ _die "$hn: '$sender' is not the master for '$repo'" if $master ne $sender;
+
+ $ENV{GL_BYPASS_CREATOR_CHECK} = option($repo, "bypass-creator-check");
+ # this expects valid perms content on STDIN
+ _system("gitolite perms -c $repo");
+ delete $ENV{GL_BYPASS_CREATOR_CHECK};
+
+ # we're done. Yes, really...
+ exit 0;
+ }
+
+ if ( $ENV{SSH_ORIGINAL_COMMAND} =~ /^USER=(\S+) SOC=(git-receive-pack '(\S+)')$/ ) {
+ # my ($user, $newsoc, $repo) = ($1, $2, $3);
+ $ENV{SSH_ORIGINAL_COMMAND} = $2;
+ @ARGV = ($1);
+ $rc{REDIRECTED_PUSH} = 1;
+ trace( 3, "redirected_push for user $1" );
+ } else {
+ # master -> copy push, no access checks needed
+ $ENV{GL_BYPASS_ACCESS_CHECKS} = 1;
+ }
+}
+
+# ----------------------------------------------------------------------
+
+sub pre_git {
+ return unless $hn;
+ # nothing, and I mean NOTHING, happens if HOSTNAME is not set
+ trace( 3, "pre_git() on $hn" );
+
+ my ( $repo, $user, $aa ) = @_[ 1, 2, 3 ];
+
+ my $sender = $rc{FROM_SERVER} || '';
+ $user = '' if $sender and not exists $rc{REDIRECTED_PUSH};
+
+ # ------------------------------------------------------------------
+ # now you know the repo, get its mirroring details
+ details($repo);
+
+ # print mirror status if at least one copy status file is present
+ print_status( $repo ) if not $rc{HUSH_MIRROR_STATUS} and $mode ne 'local' and glob("$rc{GL_REPO_BASE}/$repo.git/gl-copy-*.status");
+
+ # we don't deal with any reads. Note that for pre-git this check must
+ # happen *after* getting details, to give mode() a chance to die on "known
+ # unknown" repos (repos that are in the config, but mirror settings
+ # exclude this host from both the master and copy lists)
+ return if $aa eq 'R';
+
+ trace( 1, "mirror", "pre_git", $repo, "user=$user", "sender=$sender", "mode=$mode", ( $rc{REDIRECTED_PUSH} ? ("redirected") : () ) );
+
+ # ------------------------------------------------------------------
+ # case 1: we're master or copy, normal user pushing to us
+ if ( $user and not $rc{REDIRECTED_PUSH} ) {
+ trace( 3, "case 1, user push" );
+ return if $mode eq 'local' or $mode eq 'master';
+ if ( $trusted_copies{$hn} ) {
+ trace( 1, "redirect to $master" );
+ exec( "ssh", $master, "USER=$user", "SOC=$ENV{SSH_ORIGINAL_COMMAND}" );
+ } else {
+ _die "$hn: pushing '$repo' to copy '$hn' not allowed";
+ }
+ }
+
+ # ------------------------------------------------------------------
+ # case 2: we're copy, master pushing to us
+ if ( $sender and not $rc{REDIRECTED_PUSH} ) {
+ trace( 3, "case 2, master push" );
+ _die "$hn: '$repo' is local" if $mode eq 'local';
+ _die "$hn: '$repo' is native" if $mode eq 'master';
+ _die "$hn: '$sender' is not the master for '$repo'" if $master ne $sender;
+ return;
+ }
+
+ # ------------------------------------------------------------------
+ # case 3: we're master, copy sending a redirected push to us
+ if ( $sender and $rc{REDIRECTED_PUSH} ) {
+ trace( 3, "case 2, copy redirect" );
+ _die "$hn: '$repo' is local" if $mode eq 'local';
+ _die "$hn: '$repo' is not native" if $mode eq 'copy';
+ _die "$hn: '$sender' is not a valid copy for '$repo'" if not $copies{$sender};
+ _die "$hn: redirection not allowed from '$sender'" if not $trusted_copies{$sender};
+ return;
+ }
+
+ _die "$hn: should not reach this line";
+
+}
+
+# ----------------------------------------------------------------------
+
+sub post_git {
+ return unless $hn;
+ # nothing, and I mean NOTHING, happens if HOSTNAME is not set
+ trace( 1, "post_git() on $hn" );
+
+ my ( $repo, $user, $aa ) = @_[ 1, 2, 3 ];
+ # we don't deal with any reads
+ return if $aa eq 'R';
+
+ my $sender = $rc{FROM_SERVER} || '';
+ $user = '' if $sender;
+
+ # ------------------------------------------------------------------
+ # now you know the repo, get its mirroring details
+ details($repo);
+
+ trace( 1, "mirror", "post_git", $repo, "user=$user", "sender=$sender", "mode=$mode", ( $rc{REDIRECTED_PUSH} ? ("redirected") : () ) );
+
+ # ------------------------------------------------------------------
+ # case 1: we're master or copy, normal user pushing to us
+ if ( $user and not $rc{REDIRECTED_PUSH} ) {
+ trace( 3, "case 1, user push" );
+ return if $mode eq 'local';
+ # copy was eliminated earlier anyway, so that leaves 'master'
+
+ # find all copies and push to each of them
+ push_to_copies($repo);
+
+ return;
+ }
+
+ # ------------------------------------------------------------------
+ # case 2: we're copy, master pushing to us
+ if ( $sender and not $rc{REDIRECTED_PUSH} ) {
+ trace( 3, "case 2, master push" );
+ # nothing to do
+ return;
+ }
+
+ # ------------------------------------------------------------------
+ # case 3: we're master, copy sending a redirected push to us
+ if ( $sender and $rc{REDIRECTED_PUSH} ) {
+ trace( 3, "case 2, copy redirect" );
+
+ # find all copies and push to each of them
+ push_to_copies($repo);
+
+ return;
+ }
+}
+
+{
+ my $lastrepo = '';
+
+ sub details {
+ my $repo = shift;
+ return if $lastrepo eq $repo;
+
+ $master = master($repo);
+ %copies = copies($repo);
+ $mode = mode($repo);
+ %trusted_copies = trusted_copies($repo);
+ trace( 3, $master, $mode, join( ",", sort keys %copies ), join( ",", sort keys %trusted_copies ) );
+ }
+
+ sub master {
+ return option( +shift, 'mirror.master' );
+ }
+
+ sub copies {
+ my $repo = shift;
+
+ my $ref = git_config( $repo, "^gitolite-options\\.mirror\\.copies.*" );
+ my %out = map { $_ => 'async' } map { split } values %$ref;
+
+ $ref = git_config( $repo, "^gitolite-options\\.mirror\\.copies\\.sync.*" );
+ map { $out{$_} = 'sync' } map { split } values %$ref;
+
+ $ref = git_config( $repo, "^gitolite-options\\.mirror\\.copies\\.nosync.*" );
+ map { $out{$_} = 'nosync' } map { split } values %$ref;
+
+ return %out;
+ }
+
+ sub trusted_copies {
+ my $ref = git_config( +shift, "^gitolite-options\\.mirror\\.redirectOK.*" );
+ # the list of trusted copies (where we accept redirected pushes from)
+ # is either explicitly given...
+ my @out = map { split } values %$ref;
+ my %out = map { $_ => 1 } @out;
+ # ...or it's all the copies mentioned if the list is just a "all"
+ %out = %copies if ( @out == 1 and $out[0] eq 'all' );
+ return %out;
+ }
+
+ sub mode {
+ my $repo = shift;
+ return 'local' if not $hn;
+ return 'master' if $master eq $hn;
+ return 'copy' if $copies{$hn};
+ return 'local' if not $master and not %copies;
+ _die "$hn: '$repo' is mirrored but not here";
+ }
+}
+
+sub push_to_copies {
+ my $repo = shift;
+
+ my $u = $ENV{GL_USER};
+ delete $ENV{GL_USER}; # why? see src/commands/mirror
+
+ my $lb = "$ENV{GL_REPO_BASE}/$repo.git/.gl-mirror-lock";
+ for my $s ( sort keys %copies ) {
+ trace( 1, "push_to_copies skipping self" ), next if $s eq $hn;
+ system("gitolite 1plus1 $lb.$s gitolite mirror push $s $repo </dev/null >/dev/null 2>&1 &") if $copies{$s} eq 'async';
+ system("gitolite 1plus1 $lb.$s gitolite mirror push $s $repo </dev/null >/dev/null 2>&1") if $copies{$s} eq 'sync';
+ _warn "manual mirror push pending for '$s'" if $copies{$s} eq 'nosync';
+ }
+
+ $ENV{GL_USER} = $u;
+}
+
+sub print_status {
+ my $repo = shift;
+ my $u = $ENV{GL_USER};
+ delete $ENV{GL_USER};
+ system("gitolite mirror status all $repo >&2");
+ $ENV{GL_USER} = $u;
+}
+
+1;