#!/usr/bin/env perl #Kamin Whitehouse Oct 17 2004 #This script keeps a local working copy in synch with a subversion #repository, only updating directories with changed files (for efficiency). #It should be called by your subversion post-commit script. # #The script takes two parametrs: the path to the repos and the #revision number of which changes need to be synched. # #$reposdirectory is the directory in the repos we are trying to synch with #$exporteddirectory is the path to the local working copy #$username is the owner of the local working copy (who this script should be run as) #$dirperms is the permissions that all directories in the working copy should have #$fileperms is the permissions that all files in the working copy should have # #The dirperms and fileperms is most useful if you are trying to keep a #www directory in synch with a svn repos. They are set to 755 and 644 #by default so that all files in the working copy are world readable. # #The username is most useful if you are using apache to access your #repository. The post-commit scripts are run as the "apache" user, so #you either need to let apache run as the owner of the repository or #you need to let that user sudo to the owner of the repository for #"svn up" commands. # #This script is a modification of the standard commit-access-control.pl file that comes with the #subversion distribution. # # ==================================================================== # synchWithSvnRepos.pl: keep a local working copy in synch with a svn repos # # derived from: # $HeadURL: http://svn.collab.net/repos/svn/branches/1.0.5/tools/hook-scripts/commit-access-control.pl.in $ # $LastChangedDate: 2004-02-12 05:07:35 -0500 (Thu, 12 Feb 2004) $ # $LastChangedBy: dlr $ # $LastChangedRevision: 8621 $ # # Usage: synchWithSvnRepos.pl REPOS REV_NUM # # ==================================================================== # Copyright (c) 2000-2004 CollabNet. All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at http://subversion.tigris.org/license-1.html. # If newer versions of this license are posted there, you may use a # newer version instead, at your option. # # This software consists of voluntary contributions made by many # individuals. For exact contribution history, see the revision # history and logs, available at http://subversion.tigris.org/. # ==================================================================== # The warning switch is set here and not in the shebang line above # with /usr/bin/env because env will try to find the binary named # 'perl -w', which won't work. BEGIN { $^W = 1; } use strict; use Carp; #use Config::IniFiles 2.27; ###################################################################### # Configuration section. # web directory in repository my $reposdirectory = "html/public_html/"; # exported web directory my $exporteddirectory = "/mnt/coeus/public_html/"; #my $exporteddirectory = "/home/kamin/argus_html/"; # Svnlook path. my $svnlook = "/usr/local/bin/svnlook"; # debug file name my $debugFile = "/tmp/webUpdateDebug"; #directory perms: set updated dirs to these permissions my $dirperms = "775"; #file perms: set updated files to these permissions my $fileperms = "664"; # make sure the script is run as the following user: my $username = "kamin"; # if the script is being run as a different user than specified above, # that user must be able to sudo to the user above my $sudoprefix = ""; if($username ne $ENV{"USER"}){ $sudoprefix = "sudo -u $username"; } # Since the path to svnlook depends upon the local installation # preferences, check that the required program exists to insure that # the administrator has set up the script properly. { my $ok = 1; foreach my $program ($svnlook) { if (-e $program) { unless (-x $program) { warn "$0: required program `$program' is not executable, ", "edit $0.\n"; $ok = 0; } } else { warn "$0: required program `$program' does not exist, edit $0.\n"; $ok = 0; } } exit 1 unless $ok; } ###################################################################### # Initial setup/command-line handling. &usage unless @ARGV == 2; my $repos = shift; my $rev = shift; unless (-e $repos) { &usage("$0: repository directory `$repos' does not exist."); } unless (-d $repos) { &usage("$0: repository directory `$repos' is not a directory."); } # Define two constant subroutines to stand for read-only or read-write # access to the repository. sub ACCESS_READ_ONLY () { 'read-only' } sub ACCESS_READ_WRITE () { 'read-write' } ###################################################################### # Harvest data using svnlook. # Change into /tmp so that svnlook diff can create its .svnlook # directory. my $tmp_dir = '/tmp'; chdir($tmp_dir) or die "$0: cannot chdir `$tmp_dir': $!\n"; open(FIL,">$debugFile"); print(FIL "repos $repos, revision $rev, sudoprefix = $sudoprefix\n"); close(FIL); # Get the author from svnlook. my @svnlooklines = &read_from_process($svnlook, 'author', $repos, '-r', $rev); my $author = shift @svnlooklines; unless (length $author) { die "$0: rev `$rev' has no author.\n"; } open(FIL,">>$debugFile"); print(FIL "author $author", "\n"); close(FIL); # Figure out what directories have changed using svnlook.. my @dirs_changed = &read_from_process($svnlook, 'dirs-changed', $repos, '-r', $rev); open(FIL,">>$debugFile"); print(FIL @dirs_changed,"\n"); close(FIL); # Lose the trailing slash in the directory names if one exists, except # in the case of '/'. my $rootchanged = 0; for (my $i=0; $i<@dirs_changed; ++$i) { if ($dirs_changed[$i] eq '/') { $rootchanged = 1; } else { # print "Tried to strip slash but couldn't"; # $dirs_changed[$i] =~ s#^(.+)[/\\]$#$1#; } } # Figure out what files have changed using svnlook. my @files_changed; foreach my $line (&read_from_process($svnlook, 'changed', $repos, '-r', $rev)) { # Split the line up into the modification code and path, ignoring # property modifications. if ($line =~ /^.. (.*)$/) { push(@files_changed, $1); } } open(FIL,">>$debugFile"); print(FIL @files_changed,"\n"); close(FIL); # Create the list of all modified paths. my @changed = (@dirs_changed, @files_changed); # There should always be at least one changed path. If there are # none, then there maybe something fishy going on, so just exit now # indicating that the commit should not proceed. unless (@changed) { die "$0: no changed paths found in rev `$rev'.\n"; } # Go through all the modified directories and see if any of them are in # exporteddirectory. If so, update that directory foreach my $path (@dirs_changed) { if ( $path =~ s/^$reposdirectory/$exporteddirectory/) { #don't check to see if the directory exists because we might be adding a new directory open(FIL,">>$debugFile"); print(FIL "> svn up -N $path\n"); if(system("$sudoprefix /usr/local/bin/svn up -N $path")!=0){ print(FIL "$!\n"); } close(FIL); } } #assuming the umask of the caller sets the correct permissions, you #don't need the following code. Otherwise, uncomment this code to #change the permissions for each file/directory # foreach my $path (@changed) # { # if ( $path =~ s/^$reposdirectory/$exporteddirectory/) # { # if ( -e $path) # { # if ($path =~ /.*\/$/) #this is a directory # { # open(FIL,">>$debugFile"); # print(FIL "> chmod $dirperms $path\n"); # if(system("$sudoprefix /bin/chmod 775 $path")!=0){ # print(FIL "$!\n"); # } # close(FIL); # } # else #this is a file # { # open(FIL,">>$debugFile"); # print(FIL "> chmod $fileperms $path\n"); # if(system("$sudoprefix /bin/chmod 664 $path")!=0){ # print(FIL "$!\n"); # } # close(FIL); # } # } # else # { # open(FIL,">>$debugFile"); # print(FIL "> ERROR: no such directory \"chmod $path\n\""); # close(FIL); # } # } # } #not the right thing to do # system "svn up $exporteddirectory"; # system "find $exporteddirectory -type d -exec chmod 755 '{}' \;"; # system "find $exporteddirectory -type f -exec chmod 644 '{}' \;"; # system "find $exporteddirectory -type d -path '*.svn*' -exec chmod 750 '{}' \;"; # system "find $exporteddirectory -type f -path '*.svn*' -exec chmod 640 '{}' \;"; sub usage { warn "@_\n" if @_; die "usage: $0 REPOS REV-NUM\n"; } sub safe_read_from_pipe { unless (@_) { croak "$0: safe_read_from_pipe passed no arguments.\n"; } print "Running @_\n"; my $pid = open(SAFE_READ, '-|'); unless (defined $pid) { die "$0: cannot fork: $!\n"; } unless ($pid) { open(STDERR, ">&STDOUT") or die "$0: cannot dup STDOUT: $!\n"; exec(@_) or die "$0: cannot exec `@_': $!\n"; } my @output; while () { chomp; push(@output, $_); } close(SAFE_READ); my $result = $?; my $exit = $result >> 8; my $signal = $result & 127; my $cd = $result & 128 ? "with core dump" : ""; if ($signal or $cd) { warn "$0: pipe from `@_' failed $cd: exit=$exit signal=$signal\n"; } if (wantarray) { return ($result, @output); } else { return $result; } } sub read_from_process { unless (@_) { croak "$0: read_from_process passed no arguments.\n"; } my ($status, @output) = &safe_read_from_pipe(@_); if ($status) { if (@output) { die "$0: `@_' failed with this output:\n", join("\n", @output), "\n"; } else { die "$0: `@_' failed with no output.\n"; } } else { return @output; } }