Follow-up to r29036: Now that the "mergeinfo" transaction file is no
[svn.git] / contrib / client-side / svncopy / testsvncopy.pl.in
blob0f2439b79faf96d0dbfdb7ff1a4ea64c97b956f2
1 #! /usr/bin/perl
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 #------------------------------------------------------------------------------
43 # Include files
45 use Cwd;
46 use File::Temp 0.12 qw(tempdir tempfile);
47 use Getopt::Long 2.25;
48 use Pod::Usage;
49 use URI 1.17;
52 # Global definitions
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@';
61 # Input parameters
62 my $verbose = 0;
63 my @svn_options = ();
65 # Internal information
66 my %externals_hash;
67 my $temp_dir;
69 # Error handling
70 my @errors = ();
71 my @warnings = ();
73 # Testing-specific variables
74 my $hideerrors = 0;
77 #------------------------------------------------------------------------------
78 # Main execution block
82 # Process arguments
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() },
94 ) or Usage();
96 # Put in a signal handler to clean up any temporary directories.
97 sub catch_signal {
98 my $signal = shift;
99 warn "$0: caught signal $signal. Quitting now.\n";
100 exit 1;
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
109 my $startDir = cwd;
110 if ( $0 =~ m"(.*[\\/])[^\\/]+$" )
112 my $programDir = $1;
113 chdir( $programDir );
116 # Run the tests
117 testUpdateExternals();
119 # Check whether they passed
120 if ( 0 != scalar( @errors ) )
122 print "\n*****************************************************************\n";
123 print "Errors:\n";
124 print @errors;
126 else
128 print "*** Script passed tests ***\n";
131 # Return to the original directory
132 chdir( $startDir );
134 exit( scalar( @errors ) );
137 #------------------------------------------------------------------------------
138 # Function: testUpdateExternals
140 # Tests the script, pushing any errors onto @errors.
142 # Parameters:
143 # none
145 # Returns: none
146 #------------------------------------------------------------------------------
147 sub testUpdateExternals
149 my $failed = 0;
150 my $retval;
151 my $testsubdir = "svncopy-update";
152 my $testURL = "$testroot/$testsubdir";
153 my @testdirs = (
154 "source/dirA/dir1",
155 "source/dirA/dir2",
156 "source/dirB/dir3",
157 "wibble/dirA/dir2",
159 my $dirWithExternals = $testdirs[0];
160 my $pinnedDir = $testdirs[1];
161 my $dest = "$testURL/dest";
162 my $old_verbose = $verbose;
163 my %revisions = {};
164 my $testRev;
166 my $test_externals =
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
172 my @tests = (
173 # Updating with nothing to update
174 { sources => [ "$testURL/source/dirA/dir1", ],
175 pin => 0,
176 update => 1,
177 ext_dir => "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"
184 expected_tree => [
185 "dir1/",
188 # Updating a tree - enclosed should change, unless pinned
189 { sources => [ "$testURL/source/dirA", ],
190 pin => 0,
191 update => 1,
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"
199 expected_tree => [
200 "dirA/",
201 "dirA/dir1/",
202 "dirA/dir2/",
205 # Updating with no update - no change
206 { sources => [ "$testURL/source/dirA", ],
207 pin => 0,
208 update => 0,
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"
216 expected_tree => [
217 "dirA/",
218 "dirA/dir1/",
219 "dirA/dir2/",
222 # Updating with two sources
223 { sources => [ "$testURL/source/dirA/dir1",
224 "$testURL/source/dirB/dir3" ],
225 pin => 0,
226 update => 1,
227 ext_dir => "dir1",
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"
234 expected_tree => [
235 "dir1/",
236 "dir3/",
239 # Pinning
240 { sources => [ "$testURL/source/dirA/dir1", ],
241 pin => 1,
242 update => 0,
243 ext_dir => "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",
250 expected_tree => [
251 "dir1/",
254 # Pinning a tree
255 { sources => [ "$testURL/source/dirA", ],
256 pin => 1,
257 update => 0,
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",
265 expected_tree => [
266 "dirA/",
267 "dirA/dir1/",
268 "dirA/dir2/",
271 # Pinning with two sources
272 { sources => [ "$testURL/source/dirA/dir1",
273 "$testURL/source/dirB/dir3" ],
274 pin => 1,
275 update => 0,
276 ext_dir => "dir1",
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",
283 expected_tree => [
284 "dir1/",
285 "dir3/",
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" ) )
311 $failed = 1;
312 return;
316 # Check out the test root
317 if ( 0 != SVNCall( "co", $testURL, "$test_temp_dir/$testsubdir" ) )
319 error( "Preparatory checkout failed" );
320 $failed = 1;
321 return;
324 # Set an svn:externals on it
325 # - work our what externals we need to set (get the revision for the
326 # pinned directory)
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;
333 close($handle);
334 if ( 0 != SVNCall( "propset", "svn:externals",
335 "--file", $tmpfile,
336 "$test_temp_dir/$testsubdir/$dirWithExternals" ) )
338 error( "svn propset of svn:externals failed" );
339 $failed = 1;
340 return;
343 # And commit them
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" );
349 $failed = 1;
350 return;
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",
358 "svncopyTest",
359 "wibble",
360 "$test_temp_dir/$testsubdir/$pinnedDir" ) )
362 error( "svn propset of svncopyTest failed" );
363 $failed = 1;
364 return;
366 # And commit
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" );
372 $failed = 1;
373 return;
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" );
384 # Script parameters
385 my $message = "\"Testing svncopy.pl\"";
387 TEST: foreach my $testtype ( "HEAD", "-r" )
389 my @copy_options = @svn_options;
390 my $testno = 1;
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 ".
397 "$testRev...\n";
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" ) )
413 $failed = 1;
414 return;
418 # And commit
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" );
425 $failed = 1;
426 return;
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
440 $verbose = 0;
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} );
453 # Now do the copy
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" );
459 $failed = 1;
462 # Check that the generated tree is as expected.
463 if ( !CheckTree( $dest, @expected_tree ) )
465 # CheckTree outputs an error message if it fails
466 $failed = 1;
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
474 $failed = 1;
477 # Bomb out if we had an error
478 if ( $failed )
480 print "\n*** '$testtype' test $testno failed ***\n";
481 print "****************************************************************\n";
482 last TEST;
485 print "\n### '$testtype' test $testno passed\n";
486 print "################################################################\n";
487 $testno++;
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.
501 # Parameters:
502 # svnpath directory to create
504 # Returns: non-zero on success
505 #------------------------------------------------------------------------------
506 sub CreateTestDirectory
508 my $svnpath = $_[0];
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" );
515 return 0;
518 return 1;
522 #------------------------------------------------------------------------------
523 # Function: UpdateTestDirectory
525 # Modifies the directory in the working copy so that we can check the version
526 # copied is correct.
528 # Parameters:
529 # dir directory to modify (on file system)
531 # Returns: non-zero on success
532 #------------------------------------------------------------------------------
533 sub UpdateTestDirectory
535 my $dir = $_[0];
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'" );
542 return 0;
544 print FILE "Test file in '$dir'\n";
545 close FILE;
547 # Now add it to Subversion
548 if ( 0 != SVNCall( "add", $testfile ) )
550 error( "svn add '$testfile' failed" );
551 return 0;
554 # We're done
555 return 1;
559 #------------------------------------------------------------------------------
560 # Function: CheckTree
562 # Checks that directory structure in the subversion location matches
563 # the given tree.
565 # Parameters:
566 # svnpath Subversion location to check.
567 # expected Expected response - list of files and dirs as returned
568 # by svn list.
570 # Returns: non-zero on success
571 #------------------------------------------------------------------------------
572 sub CheckTree
574 my ( $svnpath, @expected ) = @_;
576 my ( $retval, @output ) = SVNCall( "list", "--recursive", $svnpath );
577 if ( 0 != $retval )
579 error( "svn list on '$svnpath' failed" );
580 return 0;
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 ) )
591 my $addedtext;
592 my $removedtext;
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" );
604 return 0;
607 return 1;
611 #------------------------------------------------------------------------------
612 # Function: CheckExternals
614 # Checks that the subversion location matches the given tree.
616 # Parameters:
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 #------------------------------------------------------------------------------
625 sub CheckExternals
627 my ( $svnpath, $revisions, $pinnedRev, @expected ) = @_;
628 my @new_externals;
630 ( $retval, @new_externals ) = SVNCall( "propget", "svn:externals", $svnpath );
631 if ( 0 != $retval )
633 error( "svn propget on '$svnpath' failed" );
634 return 0;
637 # Update @expected with revisions
638 @expected = grep
640 $_ =~ s|__PINREV__|$pinnedRev|g;
641 if ( $_ =~ m"(.*)\s+-r __REV__\s+(.*)" )
643 my $path = $1;
644 my $svnpath = $2;
645 my $rev = $revisions->{$svnpath};
646 $_ =~ s|__REV__|$rev|g;
649 } @expected;
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".
660 " - expected:\n ".
661 join( "\n ", @expected ) .
662 "\n - actual:\n ".
663 join( "\n ", @new_externals )
666 return 0;
669 return 1;
673 #------------------------------------------------------------------------------
674 # Function: CurrentRevision
676 # Returns the repository revision of the last change to the given object.
678 # Parameters:
679 # source The URL to check
681 # Returns: The relevant revision number
682 #------------------------------------------------------------------------------
683 sub CurrentRevision
685 my $source = shift;
687 my $old_verbose = $verbose;
688 $verbose = 0;
689 my ( $retval, @output ) = SVNCall( "log -q", $source );
690 $verbose = $old_verbose;
692 if ( 0 != $retval )
694 error( "CurrentRevision: log -q on '$source' failed" );
695 return -1;
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+) \|" )
716 return $1;
719 error( "CurrentRevision: log output not formatted as expected\n" );
721 return -1;
725 #------------------------------------------------------------------------------
726 # Function: SVNCall
728 # Makes a call to subversion.
730 # Parameters:
731 # command Subversion command
732 # options Other options to pass to Subversion
734 # Returns: exit status, output from command
735 #------------------------------------------------------------------------------
736 sub SVNCall
738 my ( $command, @options ) = @_;
740 my @commandline = ( $svn, $command, @options );
742 info( " > ", join( " ", @commandline ), "\n" );
744 my @output = qx( @commandline 2>&1 );
746 my $result = $?;
747 my $exit = $result >> 8;
748 my $signal = $result & 127;
749 my $cd = $result & 128 ? "with core dump" : "";
750 if ($signal or $cd)
752 error( "$0: 'svn $command' failed $cd: exit=$exit signal=$signal\n" );
755 if ( $exit > 0 )
757 info( join( "\n", @output ) );
759 if ( wantarray )
761 return ( $exit, @output );
764 return $exit;
768 #------------------------------------------------------------------------------
769 # Function: CreateSVNDirectories
771 # Creates a directory in Subversion, including all intermediate directories.
773 # Parameters:
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 ) = @_;
782 my $r = $URI->clone;
783 my @path_segments = grep { length($_) } $r->path_segments;
784 my @r_path_segments;
785 unshift(@path_segments, '');
786 $r->path('');
788 my $found_root = 0;
789 my $found_tail = 0;
791 # Prepare a file containing the message
792 my ($handle, $messagefile) = tempfile( DIR => $temp_dir );
793 print $handle $message;
794 close($handle);
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;
799 $verbose = 0;
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 );
806 if ( !$found_root )
808 if ( SVNCall( 'log', '-r', 'HEAD', $r ) == 0 )
810 # We've found the root of the repository.
811 $found_root = 1;
814 elsif ( !$found_tail )
816 if ( SVNCall( 'log', '-r', 'HEAD', $r ) != 0 )
818 # We've found the first directory which doesn't exist.
819 $found_tail = 1;
823 if ( $found_tail )
825 # We're creating directories
826 $verbose = $old_verbose;
827 if ( 0 != SVNCall( 'mkdir', @msgcmd, $r ) )
829 error( "Couldn't create directory '$r'" );
830 return 0;
834 $verbose = $old_verbose;
836 return 1;
840 #------------------------------------------------------------------------------
841 # Function: CompareLists
843 # Compares two lists.
845 # Parameters:
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 #------------------------------------------------------------------------------
856 sub CompareLists
858 my $context = $_[0];
859 my %count = ();
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}} )
869 $count{$element}++;
871 # Add the elements from list 2 into the hash (negative)
872 foreach $element( @{$context->{list2}} )
874 $count{$element}--;
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 #------------------------------------------------------------------------------
894 # Function: info
896 # Prints out an informational message in verbose mode
898 # Parameters:
899 # @_ The message(s) to print
901 # Returns: none
902 #------------------------------------------------------------------------------
903 sub info
905 if ( $verbose )
907 print @_;
912 #------------------------------------------------------------------------------
913 # Function: error
915 # Prints out and logs an error message
917 # Parameters:
918 # @_ The error messages
920 # Returns: none
921 #------------------------------------------------------------------------------
922 sub error
924 my $error;
926 # This is used during testing
927 if ( $hideerrors )
929 return;
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 );
937 if ( $verbose )
939 print $text;
945 #------------------------------------------------------------------------------
946 # Function: Usage
948 # Prints out usage information.
950 # Parameters:
951 # optional error message
953 # Returns: none
954 #------------------------------------------------------------------------------
955 sub Usage
957 my $msg;
958 $msg = "\n*** $_[0] ***\n" if $_[0];
960 pod2usage( { -message => $msg,
961 -verbose => 0 } );
965 #------------------------------------------------------------------------------
966 # This package exists just to delete the temporary directory.
967 #------------------------------------------------------------------------------
968 package Temp::Delete;
970 use File::Temp 0.12 qw(tempdir);
972 sub new
974 my $this = shift;
975 my $class = ref($this) || $this;
976 my $self = {};
977 bless $self, $class;
979 my $temp_dir = tempdir("testsvncopy_XXXXXXXXXX", TMPDIR => 1);
981 $self->{tempdir} = $temp_dir;
983 return $self;
986 sub temp_dir
988 my $self = shift;
989 return $self->{tempdir};
992 sub DESTROY
994 my $self = shift;
995 my $temp_dir = $self->{tempdir};
996 if ( scalar( @errors ) )
998 print "Leaving $temp_dir for inspection\n";
1000 else
1002 info( "Cleaning up $temp_dir\n" );
1003 File::Path::rmtree([$temp_dir], 0, 0);
1006 # Return to the original directory
1007 chdir( $startDir );
1011 #------------------------------------------------------------------------------
1012 # Documentation follows, in pod format.
1013 #------------------------------------------------------------------------------
1014 __END__
1016 =head1 NAME
1018 B<testsvncopy> - tests for B<svncopy> script
1020 =head1 SYNOPSIS
1022 B<testsvncopy.pl> [option ...]
1024 B<testsvncopy.pl> tests the operation of the B<svncopy.pl> script.
1026 Options:
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
1037 =head1 OPTIONS
1039 =over 8
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
1078 =item B<--help>
1080 Print a brief help message and exits
1082 =back
1084 =head1 DESCRIPTION
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.
1092 =cut
1094 #------------------------------- END OF FILE ----------------------------------