3 # testsvncopy.pl -- test script for svncopy.pl.
5 # This program is free software; you can redistribute it and/or modify it
6 # under the terms of the GNU General Public License as published by the
7 # Free Software Foundation; either version 2 of the License, or (at your
8 # option) any later version.
10 # THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
11 # WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
12 # MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
13 # NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
14 # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
15 # NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
16 # USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
17 # ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
18 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
19 # THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
21 # You should have received a copy of the GNU General Public License along
22 # with this program; if not, write to the Free Software Foundation, Inc.,
23 # 59 Temple Place - Suite 330, Boston MA 02111-1307 USA.
25 # This product makes use of software developed by
26 # CollabNet (http://www.Collab.Net/), see http://subversion.tigris.org/.
28 # This software consists of voluntary contributions made by many
29 # individuals. For exact contribution history, see the revision
30 # history and logs, available at http://subversion.tigris.org/.
31 #------------------------------------------------------------------------------
33 #------------------------------------------------------------------------------
35 # This script tests the operation of svncopy.pl.
37 # For more information see the pod documentation at the foot of the file,
38 # or run testsvncopy.pl -?.
40 #------------------------------------------------------------------------------
46 use File
::Temp
0.12 qw(tempdir tempfile);
47 use Getopt
::Long
2.25;
55 # Specify the location of the svn command.
56 my $svn = '@SVN_BINDIR@/svn';
58 # The scratch repository location for the tests
59 my $testroot = '@SVN_TEST_REPOSITORY@';
65 # Internal information
73 # Testing-specific variables
77 #------------------------------------------------------------------------------
78 # Main execution block
84 GetOptions
( "verbose!" => sub { $verbose = 1; push( @svn_options, "--verbose" ) },
85 "quiet|q" => sub { $verbose = 0; push( @svn_options, "--quiet" ) },
86 "username=s" => sub { push( @svn_options, "--username", $_[1] ) },
87 "password=s" => sub { push( @svn_options, "--password", $_[1] ) },
88 "no_auth_cache" => sub { push( @svn_options, "--no-auth-cache" ) },
89 "force-log" => sub { push( @svn_options, "--force-log" ) },
90 "encoding=s" => sub { push( @svn_options, "--encoding", $_[1] ) },
91 "config-dir=s" => sub { push( @svn_options, "--config-dir", $_[1] ) },
92 "test-repository|t=s" => \
$testroot,
93 "help|?" => sub{ Usage
() },
96 # Put in a signal handler to clean up any temporary directories.
99 warn "$0: caught signal $signal. Quitting now.\n";
103 $SIG{HUP
} = \
&catch_signal
;
104 $SIG{INT
} = \
&catch_signal
;
105 $SIG{TERM
} = \
&catch_signal
;
106 $SIG{PIPE
} = \
&catch_signal
;
108 # Make sure we're in the correct directory, saving current before we move
110 if ( $0 =~ m
"(.*[\\/])[^\\/]+$" )
113 chdir( $programDir );
117 testUpdateExternals
();
119 # Check whether they passed
120 if ( 0 != scalar( @errors ) )
122 print "\n*****************************************************************\n";
128 print "*** Script passed tests ***\n";
131 # Return to the original directory
134 exit( scalar( @errors ) );
137 #------------------------------------------------------------------------------
138 # Function: testUpdateExternals
140 # Tests the script, pushing any errors onto @errors.
146 #------------------------------------------------------------------------------
147 sub testUpdateExternals
151 my $testsubdir = "svncopy-update";
152 my $testURL = "$testroot/$testsubdir";
159 my $dirWithExternals = $testdirs[0];
160 my $pinnedDir = $testdirs[1];
161 my $dest = "$testURL/dest";
162 my $old_verbose = $verbose;
167 "DIR2 $testURL/source/dirA/dir2\n". # 1 space
168 "DIR2Pin -r __PINREV__ $testURL/source/dirA/dir2\n".
169 "DIR3 $testURL/source/dirB/dir3\n". # 5 spaces
170 "DIR4 $testURL/wibble/dirA/dir2"; # 2 tabs
173 # Updating with nothing to update
174 { sources
=> [ "$testURL/source/dirA/dir1", ],
178 expected_externals
=> [
179 "DIR2 $testURL/source/dirA/dir2",
180 "DIR2Pin -r __PINREV__ $testURL/source/dirA/dir2",
181 "DIR3 $testURL/source/dirB/dir3",
182 "DIR4 $testURL/wibble/dirA/dir2"
188 # Updating a tree - enclosed should change, unless pinned
189 { sources
=> [ "$testURL/source/dirA", ],
192 ext_dir
=> "dirA/dir1",
193 expected_externals
=> [
194 "DIR2 $testURL/dest/dirA/dir2",
195 "DIR2Pin -r __PINREV__ $testURL/source/dirA/dir2",
196 "DIR3 $testURL/source/dirB/dir3",
197 "DIR4 $testURL/wibble/dirA/dir2"
205 # Updating with no update - no change
206 { sources
=> [ "$testURL/source/dirA", ],
209 ext_dir
=> "dirA/dir1",
210 expected_externals
=> [
211 "DIR2 $testURL/source/dirA/dir2",
212 "DIR2Pin -r __PINREV__ $testURL/source/dirA/dir2",
213 "DIR3 $testURL/source/dirB/dir3",
214 "DIR4 $testURL/wibble/dirA/dir2"
222 # Updating with two sources
223 { sources
=> [ "$testURL/source/dirA/dir1",
224 "$testURL/source/dirB/dir3" ],
228 expected_externals
=> [
229 "DIR2 $testURL/source/dirA/dir2",
230 "DIR2Pin -r __PINREV__ $testURL/source/dirA/dir2",
231 "DIR3 $testURL/dest/dir3",
232 "DIR4 $testURL/wibble/dirA/dir2"
240 { sources
=> [ "$testURL/source/dirA/dir1", ],
244 expected_externals
=> [
245 "DIR2 -r __REV__ $testURL/source/dirA/dir2",
246 "DIR2Pin -r __PINREV__ $testURL/source/dirA/dir2",
247 "DIR3 -r __REV__ $testURL/source/dirB/dir3",
248 "DIR4 -r __REV__ $testURL/wibble/dirA/dir2",
255 { sources
=> [ "$testURL/source/dirA", ],
258 ext_dir
=> "dirA/dir1",
259 expected_externals
=> [
260 "DIR2 -r __REV__ $testURL/source/dirA/dir2",
261 "DIR2Pin -r __PINREV__ $testURL/source/dirA/dir2",
262 "DIR3 -r __REV__ $testURL/source/dirB/dir3",
263 "DIR4 -r __REV__ $testURL/wibble/dirA/dir2",
271 # Pinning with two sources
272 { sources
=> [ "$testURL/source/dirA/dir1",
273 "$testURL/source/dirB/dir3" ],
277 expected_externals
=> [
278 "DIR2 -r __REV__ $testURL/source/dirA/dir2",
279 "DIR2Pin -r __PINREV__ $testURL/source/dirA/dir2",
280 "DIR3 -r __REV__ $testURL/source/dirB/dir3",
281 "DIR4 -r __REV__ $testURL/wibble/dirA/dir2",
290 my $auto_temp_dir = Temp
::Delete
->new();
291 my $test_temp_dir = $auto_temp_dir->temp_dir();
292 $temp_dir = $test_temp_dir;
293 print "\n################################################################\n";
294 print( "Testing svncopy.pl\n" );
295 info
( "Using temporary directory $test_temp_dir\n" );
296 print( "Preparing source directory structure...\n" );
299 # Set up the source directory to copy
302 # Kill the directory if it's there
303 info
( " - Deleting '$testURL'\n" );
304 SVNCall
( 'delete', '-m', '"Preparing to test svncopy --update-externals"', $testURL );
306 # Now create the source directories
307 foreach my $dir ( @testdirs )
309 if ( !CreateTestDirectory
( "$testURL/$dir" ) )
316 # Check out the test root
317 if ( 0 != SVNCall
( "co", $testURL, "$test_temp_dir/$testsubdir" ) )
319 error
( "Preparatory checkout failed" );
324 # Set an svn:externals on it
325 # - work our what externals we need to set (get the revision for the
327 my $pinnedRev = CurrentRevision
( "$testURL/$pinnedDir" );
328 $test_externals =~ s
|__PINREV__
|$pinnedRev|gi
;
330 # Now write the externals to a temporary file and set them on the dir.
331 my ($handle, $tmpfile) = tempfile
( DIR
=> $test_temp_dir );
332 print $handle $test_externals;
334 if ( 0 != SVNCall
( "propset", "svn:externals",
336 "$test_temp_dir/$testsubdir/$dirWithExternals" ) )
338 error
( "svn propset of svn:externals failed" );
344 if ( 0 != SVNCall
( "commit", "-m",
345 "\"Testing svncopy --update_externals - adding svn:externals\"",
346 "$test_temp_dir/$testsubdir/$dirWithExternals" ) )
348 error
( "svn commit failed" );
354 # Also set a property on the pinned directory to make sure the pinned
355 # revision isn't the last one.
357 if ( 0 != SVNCall
( "propset",
360 "$test_temp_dir/$testsubdir/$pinnedDir" ) )
362 error
( "svn propset of svncopyTest failed" );
367 if ( 0 != SVNCall
( "commit", "-m",
368 "\"Testing svncopy --update_externals - adding svncopyTest property\"",
369 "$test_temp_dir/$testsubdir/$pinnedDir" ) )
371 error
( "svn commit failed" );
376 # Having done all the set-up, get our revision numbers.
377 foreach my $dir ( @testdirs )
379 $revisions{ "$testURL/$dir" } = CurrentRevision
( "$testURL/$dir" );
382 print( "...Source directory structure complete\n" );
385 my $message = "\"Testing svncopy.pl\"";
387 TEST
: foreach my $testtype ( "HEAD", "-r" )
389 my @copy_options = @svn_options;
392 # Do extra setup for -r
393 if ( "-r" eq $testtype )
395 $testRev = $revisions{ "$testURL/$pinnedDir" };
396 print "Updating repository to run --revision tests against revision ".
400 # Copy the same revision we did before
401 # The last thing we changed was the pinned directory, so
402 # take its revision as the one we want to copy.
404 push( @copy_options, "--revision", "$testRev" );
407 # Now add a file to each directory.
409 foreach my $dir ( @testdirs )
411 if ( !UpdateTestDirectory
( "$test_temp_dir/$testsubdir/$dir" ) )
419 if ( 0 != SVNCall
( "commit", "-m",
420 "\"Testing svncopy --update_externals".
421 " - updating directories for '--revision' test\"",
422 "$test_temp_dir/$testsubdir" ) )
424 error
( "svn commit of updated directories failed" );
429 print "...update done. Now re-running tests against new repository\n";
432 foreach my $test ( @tests )
434 my @cmd_options = @copy_options;
436 print "\n################################################################\n";
437 print "### test number $testno\n";
439 # Kill the destination directory if it's there
441 SVNCall
( 'delete', '-m', '"Preparing to test svncopy --update-externals"', $dest );
442 $verbose = $old_verbose;
444 my @sources = @
{$test->{sources
}};
445 my @expected_externals = @
{$test->{expected_externals
}};
446 my @expected_tree = @
{$test->{expected_tree
}};
448 # Update global parameters
449 push( @cmd_options, "--message", "$message" );
450 push( @cmd_options, "--tag" ) if ( $test->{pin
} );
451 push( @cmd_options, "--branch" ) if ( $test->{update
} );
454 my @cmdline = ( "perl", "svncopy.pl", @cmd_options, @sources, $dest );
455 info
( "\n=> Calling ", join( " ", @cmdline ), "\n\n" );
456 if ( system( @cmdline ) )
458 error
( "Copy failed" );
462 # Check that the generated tree is as expected.
463 if ( !CheckTree
( $dest, @expected_tree ) )
465 # CheckTree outputs an error message if it fails
469 # And check the externals
470 my $ext_dir = "$dest/$test->{ext_dir}";
471 if ( !CheckExternals
( $ext_dir, \
%revisions, $pinnedRev, @expected_externals ) )
473 # CheckExternals outputs an error message if it fails
477 # Bomb out if we had an error
480 print "\n*** '$testtype' test $testno failed ***\n";
481 print "****************************************************************\n";
485 print "\n### '$testtype' test $testno passed\n";
486 print "################################################################\n";
491 if ( $failed ) { error
( "*** svncopy tests failed\n" ); }
492 else { print "... svncopy tests passed\n"; }
496 #------------------------------------------------------------------------------
497 # Function: CreateTestDirectory
499 # Creates a directory in svn.
502 # svnpath directory to create
504 # Returns: non-zero on success
505 #------------------------------------------------------------------------------
506 sub CreateTestDirectory
510 my $test_uri = URI
->new( "$svnpath" );
511 info
( "Creating '$test_uri'\n" );
512 if ( !CreateSVNDirectories
( $test_uri, "Testing svncopy --update_externals" ) )
514 error
( "CreateSVNDirectories on '$test_uri' failed" );
522 #------------------------------------------------------------------------------
523 # Function: UpdateTestDirectory
525 # Modifies the directory in the working copy so that we can check the version
529 # dir directory to modify (on file system)
531 # Returns: non-zero on success
532 #------------------------------------------------------------------------------
533 sub UpdateTestDirectory
536 my $testfile = "$dir/test.txt";
538 # Create a file in the directory
539 if ( !open FILE
, ">$testfile" )
541 error
( "Couldn't create test file '$testfile'" );
544 print FILE
"Test file in '$dir'\n";
547 # Now add it to Subversion
548 if ( 0 != SVNCall
( "add", $testfile ) )
550 error
( "svn add '$testfile' failed" );
559 #------------------------------------------------------------------------------
560 # Function: CheckTree
562 # Checks that directory structure in the subversion location matches
566 # svnpath Subversion location to check.
567 # expected Expected response - list of files and dirs as returned
570 # Returns: non-zero on success
571 #------------------------------------------------------------------------------
574 my ( $svnpath, @expected ) = @_;
576 my ( $retval, @output ) = SVNCall
( "list", "--recursive", $svnpath );
579 error
( "svn list on '$svnpath' failed" );
583 # Remove any blank lines and carriage returns
584 @output = grep( { chomp($_); $_ !~ m
"^\s*$"} @output );
586 # Now compare with expected
587 my $compare_ctx = { list1
=> [@expected], list2
=> [@output] };
589 if ( 0 != CompareLists
( $compare_ctx ) )
594 if ( @
{$compare_ctx->{added
}} )
596 $addedtext = "\n +".join( "\n +", @
{$compare_ctx->{added
}} );
598 if ( @
{$compare_ctx->{removed
}} )
600 $removedtext = "\n -".join( "\n -", @
{$compare_ctx->{removed
}} );
602 error
( "'$svnpath' doesn't match expected$addedtext$removedtext\n" );
611 #------------------------------------------------------------------------------
612 # Function: CheckExternals
614 # Checks that the subversion location matches the given tree.
617 # svnpath Subversion location to check.
618 # revisions Hash containing the revisions for externals.
619 # pinnedRev Revision of pinned directory.
620 # expected Expected response - list of externals as returned
621 # by svn propget svn:externals.
623 # Returns: non-zero on success
624 #------------------------------------------------------------------------------
627 my ( $svnpath, $revisions, $pinnedRev, @expected ) = @_;
630 ( $retval, @new_externals ) = SVNCall
( "propget", "svn:externals", $svnpath );
633 error
( "svn propget on '$svnpath' failed" );
637 # Update @expected with revisions
640 $_ =~ s
|__PINREV__
|$pinnedRev|g
;
641 if ( $_ =~ m
"(.*)\s+-r __REV__\s+(.*)" )
645 my $rev = $revisions->{$svnpath};
646 $_ =~ s
|__REV__
|$rev|g
;
651 # Remove any blank lines and carriage returns from the output
652 @new_externals = grep( { chomp($_); $_ !~ m
"^\s*$"} @new_externals );
654 # Now compare with expected
655 my $compare_ctx = { list1
=> [@expected], list2
=> [@new_externals] };
657 if ( 0 != CompareLists
( $compare_ctx ) )
659 error
( "Externals on '$svnpath' don't match expected\n".
661 join( "\n ", @expected ) .
663 join( "\n ", @new_externals )
673 #------------------------------------------------------------------------------
674 # Function: CurrentRevision
676 # Returns the repository revision of the last change to the given object.
679 # source The URL to check
681 # Returns: The relevant revision number
682 #------------------------------------------------------------------------------
687 my $old_verbose = $verbose;
689 my ( $retval, @output ) = SVNCall
( "log -q", $source );
690 $verbose = $old_verbose;
694 error
( "CurrentRevision: log -q on '$source' failed" );
699 # The second line should give us the info we need: e.g.
701 # >svn log -q http://subversion/svn/scratch/ianb/svncopy-update/source/dirA
702 # ------------------------------------------------------------------------
703 # r1429 | ib | 2004-06-14 17:39:36 +0100 (Mon, 14 Jun 2004)
704 # ------------------------------------------------------------------------
705 # r1423 | ib | 2004-06-14 17:39:26 +0100 (Mon, 14 Jun 2004)
706 # ------------------------------------------------------------------------
707 # r1422 | ib | 2004-06-14 17:39:23 +0100 (Mon, 14 Jun 2004)
708 # ------------------------------------------------------------------------
709 # r1421 | ib | 2004-06-14 17:39:22 +0100 (Mon, 14 Jun 2004)
710 # ------------------------------------------------------------------------
712 # The second line starts with the latest revision number.
714 if ( $output[1] =~ m
"^r(\d+) \|" )
719 error
( "CurrentRevision: log output not formatted as expected\n" );
725 #------------------------------------------------------------------------------
728 # Makes a call to subversion.
731 # command Subversion command
732 # options Other options to pass to Subversion
734 # Returns: exit status, output from command
735 #------------------------------------------------------------------------------
738 my ( $command, @options ) = @_;
740 my @commandline = ( $svn, $command, @options );
742 info
( " > ", join( " ", @commandline ), "\n" );
744 my @output = qx( @commandline 2>&1 );
747 my $exit = $result >> 8;
748 my $signal = $result & 127;
749 my $cd = $result & 128 ?
"with core dump" : "";
752 error
( "$0: 'svn $command' failed $cd: exit=$exit signal=$signal\n" );
757 info
( join( "\n", @output ) );
761 return ( $exit, @output );
768 #------------------------------------------------------------------------------
769 # Function: CreateSVNDirectories
771 # Creates a directory in Subversion, including all intermediate directories.
774 # URI directory path to create.
775 # message commit message (optional).
777 # Returns: 1 on success, 0 on error
778 #------------------------------------------------------------------------------
779 sub CreateSVNDirectories
781 my ( $URI, $message ) = @_;
783 my @path_segments = grep { length($_) } $r->path_segments;
785 unshift(@path_segments, '');
791 # Prepare a file containing the message
792 my ($handle, $messagefile) = tempfile
( DIR
=> $temp_dir );
793 print $handle $message;
795 my @msgcmd = ( "--file", $messagefile );
797 # We're going to get errors while we do this. Don't show the user.
798 my $old_verbose = $verbose;
800 # Find the repository root
801 while (@path_segments)
803 my $segment = shift @path_segments;
804 push( @r_path_segments, $segment );
805 $r->path_segments( @r_path_segments );
808 if ( SVNCall
( 'log', '-r', 'HEAD', $r ) == 0 )
810 # We've found the root of the repository.
814 elsif ( !$found_tail )
816 if ( SVNCall
( 'log', '-r', 'HEAD', $r ) != 0 )
818 # We've found the first directory which doesn't exist.
825 # We're creating directories
826 $verbose = $old_verbose;
827 if ( 0 != SVNCall
( 'mkdir', @msgcmd, $r ) )
829 error
( "Couldn't create directory '$r'" );
834 $verbose = $old_verbose;
840 #------------------------------------------------------------------------------
841 # Function: CompareLists
843 # Compares two lists.
846 # context Structure containing the current state of the comparison:
847 # list1 [in] first list
848 # list2 [in] second list
849 # diffs [out] The number of differences
850 # added [out] The entries in list2 not in list1
851 # removed [out] The entries in list1 not in list2
852 # common [out] The entries in both lists
854 # Returns: The number of differences
855 #------------------------------------------------------------------------------
861 # Make sure everything's clean
862 @
{$context->{added
}} = ();
863 @
{$context->{removed
}} = ();
864 @
{$context->{common
}} = ();
866 # Add the elements from list 1 into the hash
867 foreach $element( @
{$context->{list1
}} )
871 # Add the elements from list 2 into the hash (negative)
872 foreach $element( @
{$context->{list2
}} )
877 # Now elements in list1 only have a count of 1, in list2 only have a
878 # count of -1, and in both have a count of 0
879 foreach $element ( keys %count )
881 if ( 1 == $count{$element} ) { push( @
{$context->{removed
}}, $element ); }
882 elsif ( 0 == $count{$element} ) { push( @
{$context->{common
}}, $element ); }
883 else { push( @
{$context->{added
}}, $element ); }
886 $context->{diffs
} = scalar( @
{$context->{added
}} ) +
887 scalar( @
{$context->{removed
}} );
889 return $context->{diffs
};
893 #------------------------------------------------------------------------------
896 # Prints out an informational message in verbose mode
899 # @_ The message(s) to print
902 #------------------------------------------------------------------------------
912 #------------------------------------------------------------------------------
915 # Prints out and logs an error message
918 # @_ The error messages
921 #------------------------------------------------------------------------------
926 # This is used during testing
932 # Now print out each error message and add it to the list.
933 foreach $error ( @_ )
935 my $text = "svncopy.pl: $error\n";
936 push( @errors, $text );
945 #------------------------------------------------------------------------------
948 # Prints out usage information.
951 # optional error message
954 #------------------------------------------------------------------------------
958 $msg = "\n*** $_[0] ***\n" if $_[0];
960 pod2usage
( { -message
=> $msg,
965 #------------------------------------------------------------------------------
966 # This package exists just to delete the temporary directory.
967 #------------------------------------------------------------------------------
968 package Temp
::Delete
;
970 use File
::Temp
0.12 qw(tempdir);
975 my $class = ref($this) || $this;
979 my $temp_dir = tempdir
("testsvncopy_XXXXXXXXXX", TMPDIR
=> 1);
981 $self->{tempdir
} = $temp_dir;
989 return $self->{tempdir
};
995 my $temp_dir = $self->{tempdir
};
996 if ( scalar( @errors ) )
998 print "Leaving $temp_dir for inspection\n";
1002 info
( "Cleaning up $temp_dir\n" );
1003 File
::Path
::rmtree
([$temp_dir], 0, 0);
1006 # Return to the original directory
1011 #------------------------------------------------------------------------------
1012 # Documentation follows, in pod format.
1013 #------------------------------------------------------------------------------
1018 B<testsvncopy> - tests for B<svncopy> script
1022 B<testsvncopy.pl> [option ...]
1024 B<testsvncopy.pl> tests the operation of the B<svncopy.pl> script.
1027 -t [--test-repository] : URL to repository for root of tests
1028 -q [--quiet] : print as little as possible
1029 --username arg : specify a username ARG
1030 --password arg : specify a password ARG
1031 --no-auth-cache : do not cache authentication tokens
1032 --force-log : force validity of log message source
1033 --encoding arg : treat value as being in charset encoding ARG
1034 --config-dir arg : read user configuration files from directory ARG
1035 --[no]verbose : set the script to give lots of output
1041 =item B<-t [--test-repository]>
1043 Specify a URL to a scratch area of repository which the tests can use.
1044 This can be any valid repository URL.
1046 =item B<-q [--quiet]>
1048 Print as little as possible
1050 =item B<--username arg>
1052 Specify a username ARG
1054 =item B<--password arg>
1056 Specify a password ARG
1058 =item B<--no-auth-cache>
1060 Do not cache authentication tokens
1062 =item B<--force-log>
1064 Force validity of log message source
1066 =item B<--encoding arg>
1068 Treat value as being in charset encoding ARG
1070 =item B<--config-dir arg>
1072 Read user configuration files from directory ARG
1074 =item B<--[no]verbose>
1076 Set the script to give lots of output when it runs
1080 Print a brief help message and exits
1086 B<svncopy.pl> is a utility script which performs an B<svn copy> command.
1087 It allows extra processing to get around some limitations of the B<svn copy>
1088 command (in particular related to branching and tagging).
1090 B<testsvncopy.pl> tests the operation of this script.
1094 #------------------------------- END OF FILE ----------------------------------