summaryrefslogtreecommitdiffstats
path: root/src/commands/rsync
diff options
context:
space:
mode:
Diffstat (limited to '')
-rwxr-xr-xsrc/commands/rsync143
1 files changed, 143 insertions, 0 deletions
diff --git a/src/commands/rsync b/src/commands/rsync
new file mode 100755
index 0000000..c7b25d1
--- /dev/null
+++ b/src/commands/rsync
@@ -0,0 +1,143 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+
+use lib $ENV{GL_LIBDIR};
+use Gitolite::Easy;
+
+=for admins
+
+BUNDLE SUPPORT
+
+ (1) For each repo in gitolite.conf for which you want bundle support (or
+ '@all', if you wish), add the following line:
+
+ option bundle = 1
+
+ Or you can say:
+
+ option bundle.ttl = <number>
+
+ A bundle file that is more than <number> seconds old (default value
+ 86400, i.e., 1 day) is recreated on the next bundle request. Increase
+ this if your repo is not terribly active.
+
+ Note: a bundle file is also deleted and recreated if it contains a ref
+ that was then either deleted or rewound in the repo. This is checked
+ on every invocation.
+
+ (2) Add 'rsync' to the ENABLE list in the rc file
+
+=cut
+
+=for usage
+rsync helper for gitolite
+
+BUNDLE SUPPORT
+
+ Admins: see src/commands/rsync for setup instructions
+
+ Users:
+ rsync git@host:repo.bundle .
+ # downloads a file called "<basename of repo>.bundle"; repeat as
+ # needed till the whole thing is downloaded
+ git clone repo.bundle repo
+ cd repo
+ git remote set-url origin git@host:repo
+ git fetch origin # and maybe git pull, etc. to freshen the clone
+
+ NOTE on options to the rsync command: you are only allowed to use the
+ "-v", "-n", "-q", and "-P" options.
+
+=cut
+
+usage() if not @ARGV or $ARGV[0] eq '-h';
+
+# rsync driver program. Several things can be done later, but for now it
+# drives just the 'bundle' transfer.
+
+if ( $ENV{SSH_ORIGINAL_COMMAND} =~ /^rsync --server --sender (?:-[vn]*(?:e\d*\.\w*)? )?\. (\S+)\.bundle$/ ) {
+
+ my $repo = $1;
+ $repo =~ s/\.git$//;
+
+ # all errors have the same message to avoid leaking info
+ can_read($repo) or _die "you are not authorised";
+ my %config = config( $repo, "gitolite-options.bundle" ) or _die "you are not authorised";
+
+ my $ttl = $config{'gitolite-options.bundle.ttl'} || 86400; # in seconds (default 1 day)
+
+ my $bundle = bundle_create( $repo, $ttl );
+
+ $ENV{SSH_ORIGINAL_COMMAND} =~ s( \S+\.bundle)( $bundle);
+ trace( 1, "rsync bundle", $ENV{SSH_ORIGINAL_COMMAND} );
+ Gitolite::Common::_system( split ' ', $ENV{SSH_ORIGINAL_COMMAND} );
+ exit 0;
+}
+
+_warn "Sorry, you are only allowed to use the '-v', '-n', '-q', and '-P' options.";
+usage();
+
+# ----------------------------------------------------------------------
+# helpers
+# ----------------------------------------------------------------------
+
+sub bundle_create {
+ my ( $repo, $ttl ) = @_;
+ my $bundle = "$repo.bundle";
+ $bundle =~ s(.*/)();
+ my $recreate = 0;
+
+ my ( %b, %r );
+ if ( -f $bundle ) {
+ %b = map { chomp; reverse split; } `git ls-remote --heads --tags $bundle`;
+ %r = map { chomp; reverse split; } `git ls-remote --heads --tags .`;
+
+ for my $ref ( sort keys %b ) {
+
+ my $mtime = ( stat $bundle )[9];
+ if ( time() - $mtime > $ttl ) {
+ trace( 1, "bundle too old" );
+ $recreate++;
+ last;
+ }
+
+ if ( not $r{$ref} ) {
+ trace( 1, "ref '$ref' deleted in repo" );
+ $recreate++;
+ last;
+ }
+
+ if ( $r{$ref} eq $b{$ref} ) {
+ # same on both sides; ignore
+ delete $r{$ref};
+ delete $b{$ref};
+ next;
+ }
+
+ `git rev-list --count --left-right $b{$ref}...$r{$ref}` =~ /^(\d+)\s+(\d+)$/ or _die "git too old";
+ if ($1) {
+ trace( 1, "ref '$ref' rewound in repo" );
+ $recreate++;
+ last;
+ }
+
+ }
+
+ } else {
+ trace( 1, "no bundle found" );
+ $recreate++;
+ }
+
+ return $bundle if not $recreate;
+
+ trace( 1, "creating bundle for '$repo'" );
+ -f $bundle and ( unlink $bundle or die "a horrible death" );
+ system("git bundle create $bundle --branches --tags >&2");
+
+ return $bundle;
+}
+
+sub trace {
+ Gitolite::Common::trace(@_);
+}