#!/usr/bin/perl use warnings; use strict; ### On other systems you might need to change the hard coded path to ### the various external tools we use here. ### Particulary on windows these look something like ### $svnadmin = 'c:/subversion/bin/svnadmin.exe'; my $svnadmin = "/usr/bin/svnadmin"; my $svnlook = "/usr/bin/svnlook"; my $gzip = "/bin/gzip"; ### there are many options to the script that you don't necessarily want to ### always supply on the command line ### You can put some default values in here. my %opts = ( parent => '', backup => '', repo => '', ); #################################################### use Getopt::Long qw(GetOptions); use File::Basename qw(basename); use POSIX (); use Data::Dumper; use Fcntl ':flock'; my $VERSION = '0.03'; GetOptions(\%opts, "parent=s", "repo=s", "backup=s", "incremental", "clean", "help", ) or usage(); help() if $opts{help}; usage('Need either --clean or --incremental') if not $opts{clean} and not $opts{incremental}; usage('Need --backup DIR') if not $opts{backup}; usage('Neet either --parent DIR or --repo DIR') if not $opts{parent} and not $opts{repo}; my $logfile = "$opts{backup}/last_backup.txt"; my @repos = (<$opts{parent}/*>, $opts{repo}); opendir my $dh, $opts{backup} or die "Could not open '$opts{backup}': $!"; my @backupfiles = sort readdir($dh); close $dh; foreach my $repo (@repos) { chomp $repo; next if $repo eq ''; if (not -d $repo) { warn "$repo is not a directory\n"; next; } my $reponame = basename($repo); print "Backing up '$reponame'\n"; my $file = $reponame . POSIX::strftime("-%Y%m%d-%H%M%S", localtime()); chomp(my $youngest = `$svnlook youngest $repo`); my $incremental = $opts{incremental} ? '--incremental' : ''; my ($last_backup, @old_files) = get_last_backup({ reponame => $reponame, backupfiles => \@backupfiles, backupdir => $opts{backup}, }); my $start = 0; if ($incremental and $last_backup) { $start = $last_backup +1; } else { } print "Last: $last_backup Youngest: $youngest\n"; print "@old_files\n"; next; if ($last_backup == $youngest) { print "Skipping backup as repo $repo did not change\n"; next; } $file .= "-r$start-$youngest"; my $dump = "$svnadmin dump $incremental --revision $start:$youngest $repo > $opts{backup}/$file"; if ($opts{incremental}) { print "incremental $repo to $file\n"; } else { print "full $repo to $file\n"; } print "$dump\n"; system $dump; system "$gzip $opts{backup}/$file"; save_logfile($logfile, $reponame, $youngest); unlink @old_files if $opts{clean}; } sub get_last_backup { my ($args) = @_; # TODO check if the backup files are contionous? my $last_backup = 0; my @old_files; my $reponame = $args->{reponame}; my $found_latest; foreach my $file (@{ $args->{backupfiles} }) { if ($file =~ /^$reponame-\d{8}-\d{6}-r(\d+)-(\d+).gz$/) { my ($from, $to) = ($1, $2); if ($args->{sizelimit}) { if ($found_latest) { push @old_files, $file; } else { if (-s "$ars->{backupdir}/$file" > $args->{sizelimit}) { } } $last_backup = $to if $to > $last_backup; push @old_files, $file; } elsif ($file =~ /^$reponame-/) { warn "Invalid backup file encountered: '$file' for $reponame\n"; } } return ($last_backup, @old_files); } # incremental # if no backup file yet => error, or fallback to clean backup # else find the biggest number # dump --incrementl $lates $youngest # clean # if no limit find all the files to be deleted # dump 0 $youngest # else find first file that is not over the limit (while keeping the latest), # staring from that file till the end delete them sub save_logfile { my ($logfile, $reponame, $youngest) = @_; if (not -e $logfile) { open my $tmp, ">", $logfile or die "Could not create emptu $logfile $!"; close $tmp; } open my $fh, "+<", $logfile or die "Could not open $logfile for read and write $!"; flock($fh, LOCK_EX); my @data = <$fh>; seek $fh, 0, 0; truncate $fh, 0; my $found; foreach (@data) { if ($_ =~ s/$reponame=(.*)/$reponame=$youngest/) { $found = 1; last; } } if (not $found) { push @data, "$reponame=$youngest\n"; } print $fh @data; close $fh; } sub usage { my ($msg) = @_; $msg ||= ''; print <<"END_USAGE"; $msg Usage: $0 --clean full dump from revision 0 --incremental incremental dump from the latest --parent DIR parent director with one or more SVN repositories in it --repo path to a single repository --backup DIR the directory where the backup files should be placed --help more help END_USAGE exit; } sub help { print <<"END_HELP"; Subversion repository backup v$VERSION Copyright 2006-2007 Gabor Szabo License, this is free software that comes with NO WARRANTY. You can use this script everywhere where you can use Perl5. You can redistribute it either under the GPL or Artistic license. Usually one would put a scheduled (e.g. crontab) call to $0 --clean ... once a week and $0 --incremental once a day END_HELP usage(); }