3 # svncopy.pl -- Utility script for copying with branching/tagging.
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 copies one Subversion location to another, in the same way as
36 # svn copy. Using the script allows proper branching and tagging of URLs
37 # containing svn:externals definitions.
39 # For more information see the pod documentation at the foot of the file,
40 # or run svncopy.pl -?.
42 #------------------------------------------------------------------------------
48 use File
::Temp
0.12 qw(tempdir tempfile);
49 use Getopt
::Long
2.25;
57 # Specify the location of the svn command.
58 my $svn = '@SVN_BINDIR@/svn';
63 my $pin_externals = 0;
64 my $update_externals = 0;
70 # Internal information
78 # Testing-specific variables
82 #------------------------------------------------------------------------------
83 # Main execution block
89 GetOptions
( "pin-externals|tag|t" => \
$pin_externals,
90 "update-externals|branch|b" => \
$update_externals,
91 "message|m=s" => \
$message,
92 "revision|r=s" => \
$revision,
93 "verbose!" => \
$verbose,
94 "quiet|q" => sub { $verbose = 0; push( @svn_options, "--quiet" ) },
95 "file|F=s" => sub { push( @svn_options, "--file", $_[1] ) },
96 "username=s" => sub { push( @svn_options, "--username", $_[1] ) },
97 "password=s" => sub { push( @svn_options, "--password", $_[1] ) },
98 "no_auth_cache" => sub { push( @svn_options, "--no-auth-cache" ) },
99 "force-log" => sub { push( @svn_options, "--force-log" ) },
100 "encoding=s" => sub { push( @svn_options, "--encoding", $_[1] ) },
101 "config-dir=s" => sub { push( @svn_options, "--config-dir", $_[1] ) },
102 "help|?" => sub{ Usage
() },
105 # Put in a signal handler to clean up any temporary directories.
108 warn "$0: caught signal $signal. Quitting now.\n";
112 $SIG{HUP
} = \
&catch_signal
;
113 $SIG{INT
} = \
&catch_signal
;
114 $SIG{TERM
} = \
&catch_signal
;
115 $SIG{PIPE
} = \
&catch_signal
;
118 # Check our parameters
122 Usage
( "Please specify source and destination" );
127 # Get source(s) and destination.
129 push ( @sources, shift( @ARGV ) );
130 $destination = shift( @ARGV );
131 while ( scalar( @ARGV ) )
133 push( @sources, $destination );
134 $destination = shift( @ARGV );
138 # Any validation errors? If so, bomb out.
140 if ( scalar( @errors ) > 0 )
144 exit scalar( @errors );
148 # Now do the main processing.
149 # This will update @errors if anything goes wrong.
151 if ( !DoCopy
( \
@sources, $destination, $message ) )
153 print "\n*****************************************************************\n";
158 exit scalar( @errors );
161 #------------------------------------------------------------------------------
164 # Does the work of the copy.
167 # sources Reference to array containing source URLs
168 # destination Destination URL
169 # message Commit message to use
171 # Returns: 0 on error
174 #------------------------------------------------------------------------------
177 my ( $sourceref, $destination, $message ) = @_;
178 my @sources = @
$sourceref;
182 my $starterrors = scalar( @errors );
184 print "\n=================================================================\n";
185 $revstr = "\@$revision" if $revision;
186 print "=== Copying from:\n";
187 foreach $src ( @sources ) { print "=== $src$revstr\n"; }
189 print "=== Copying to:\n";
190 print "=== $destination\n";
192 print "=== - branching (updating fully-contained svn:externals definitions)\n" if $update_externals;
193 if ( $pin_externals )
195 my $revtext = $revision ?
"revision $revision" : "current revision";
196 print "=== - tagging (pinning all svn:externals definitions to $revtext)\n";
198 print "===\n" if ( $update_externals or $pin_externals );
200 # Convert destination to URI
201 $destination =~ s
|/*$||;
202 my $destination_uri = URI
->new($destination);
205 # Generate a message if we don't have one.
209 $message = "svncopy.pl: Copied to '$destination'\n";
210 foreach $src ( @sources )
212 $message .= " Copied from '$src'\n";
217 # Create a temporary directory to work in.
219 my ( $auto_temp_dir, $dest_dir ) =
220 PrepareDirectory
( $destination_uri, "svncopy.pl to '$destination'\n - creating intermediate directory" );
221 $temp_dir = $auto_temp_dir->temp_dir();
224 foreach $src ( @sources )
226 # Convert source to URI
228 my $source_uri = URI
->new($src);
231 # Do the initial copy into our temporary. Note this will create a
232 # subdirectory with the same name as the last directory in $source.
234 if ( !CopyToWorkDir
( $src, $dest_dir ) )
236 error
( "Copy failed" );
244 if ( $pin_externals or $update_externals )
246 if ( !UpdateExternals
( $sourceref, $destination, $dest_dir, \
$message ) )
248 error
( "Couldn't update svn:externals" );
254 # And check in the new.
256 DoCommit
( $dest_dir, $message ) or die "Couldn't commit\n";
258 # Make sure we finish in the directory we started
261 print "=== ... copy complete\n";
262 print "=================================================================\n";
264 # Return whether there was an error.
265 return ( scalar( @errors ) == $starterrors );
269 #------------------------------------------------------------------------------
270 # Function: PrepareDirectory
272 # Prepares a temporary directory to work in.
275 # destination Destination URI
276 # message Commit message
278 # Returns: temporary directory and subdirectory to work in
279 #------------------------------------------------------------------------------
282 my ( $destination, $message ) = @_;
284 my $auto_temp_dir = Temp
::Delete
->new();
285 $temp_dir = $auto_temp_dir->temp_dir();
286 info
( "Using temporary directory $temp_dir\n" );
289 # Our working destination directory has the same name as the last directory
290 # in the destination URI.
292 my @path_segments = grep { length($_) } $destination->path_segments;
293 my $new_dir = pop( @path_segments );
294 my $dest_dir = "$temp_dir/$new_dir";
296 # Make sure the destination directory exists in Subversion.
297 info
( "Creating intermediate directories (if necessary)\n" );
298 if ( !CreateSVNDirectories
( $destination, $message ) )
300 error
( "Couldn't create parent directories for '$destination'" );
304 # Check out the destination.
305 info
( "Checking out destination directory '$destination'\n" );
306 if ( 0 != SVNCall
( 'co', $destination, $dest_dir ) )
308 error
( "Couldn't check out '$destination' into work directory." );
312 return ( $auto_temp_dir, $dest_dir );
316 #------------------------------------------------------------------------------
317 # Function: CopyToWorkDir
319 # Does the svn copy into the temporary directory.
322 # source The URI to copy from
323 # work_dir The working directory
325 # Returns: 1 on success
326 #------------------------------------------------------------------------------
329 my ( $source, $work_dir ) = @_;
330 my $dest_dir = DestinationSubdir
( $source, $work_dir );
331 my @commandline = ();
333 push( @commandline, "--revision", $revision ) if ( $revision );
335 push( @commandline, $source, $work_dir );
337 my $exit = SVNCall
( "copy", @commandline );
339 error
( "$0: svn copy failed" ) if ( 0 != $exit );
341 return ( 0 == $exit );
345 #------------------------------------------------------------------------------
346 # Function: DestinationSubdir
348 # Returns the destination directory for a given source and a destination root
352 # source The URL to copy from
353 # destination The working directory
355 # Returns: The relevant directory
356 #------------------------------------------------------------------------------
357 sub DestinationSubdir
359 my ( $source, $destination ) = @_;
362 # Make sure source and destination are consistent about separator.
363 # Note every function we call can handle Unix path format, so we
366 $destination =~ s
|\\|/|g
;
368 # Find the last directory - that's the subdir we'll use in $destination
369 if ( $source =~ m
"/([^/]+)/*$" )
377 return "$destination/$subdir";
381 #------------------------------------------------------------------------------
382 # Function: UpdateExternals
384 # Updates the svn:externals in the tree according to the --pin-externals or
385 # --update_externals options.
388 # sourceref Ref to the URLs to copy from
389 # destination The URL being copied to
390 # work_dir The working directory
391 # msgref Ref to message string to update with changes
393 # Returns: 1 on success
394 #------------------------------------------------------------------------------
397 my ( $sourceref, $destination, $work_dir, $msgref ) = @_;
398 my @commandline = ();
403 # Check the externals on this directory and subdirectories
404 info
( "Checking '$work_dir'\n" );
405 %extlist = GetRecursiveExternals
( $work_dir );
408 while ( my ( $subdir, $exts ) = each ( %extlist ) )
410 my @externals = @
$exts;
411 if ( scalar( @externals ) )
413 UpdateExternalsOnDir
( $sourceref, $destination, $subdir, $msgref, \
@externals );
421 #------------------------------------------------------------------------------
422 # Function: UpdateExternalsOnDir
424 # Updates the svn:externals in the tree according to the --pin-externals or
425 # --update_externals options.
428 # sourceref Ref to the URLs to copy from
429 # destination The URL being copied to
430 # work_dir The working directory
431 # externals Ref to the externals on the directory
432 # msgref Ref to message string to update with changes
434 # Returns: 1 on success
435 #------------------------------------------------------------------------------
436 sub UpdateExternalsOnDir
438 my ( $sourceref, $destination, $work_dir, $msgref, $externalsref ) = @_;
439 my @sources = @
$sourceref;
440 my @externals = @
$externalsref;
444 # Do any updating required
445 foreach my $external ( @externals )
448 next unless ( $external =~ m
"^(\S+)(\s+)(?:-r\s*(\d+)\s+)?(.*)" );
449 my ( $ext_dir, $spacing, $ext_rev, $ext_val ) = ( $1, $2, $3, $4 );
451 info
( " - Found $ext_dir => '$ext_val'" );
452 info
( " ($ext_rev)" ) if $ext_rev;
455 $externals_hash{ "$ext_val" } = $ext_rev;
457 # Only update if it's not pinned to a version
460 if ( $update_externals )
462 my $old_external = $external;
463 foreach my $source ( @sources )
465 my $dest_dir = DestinationSubdir
( $source, $destination );
466 #info( "Checking against '$source'\n" );
467 if ( $ext_val =~ s
|^$source|$dest_dir| )
469 $external = "$ext_dir$spacing$ext_val";
470 info
( " - updated '$old_external' to '$external'\n" );
471 $changed{$old_external} = $external;
475 elsif ( $pin_externals )
477 # Find the last revision of the destination and pin to that.
478 my $old_external = $external;
479 my $rev = LatestRevision
( $ext_val, $revision );
480 #info( "Pinning '$ext_val' to '$rev'\n" );
481 $external = "$ext_dir -r $rev$spacing$ext_val";
482 info
( " - updated '$old_external' to '$external'\n" );
483 $changed{$old_external} = $external;
486 push( @new_externals, $external );
489 # And write the changes back
490 if ( scalar( %changed ) )
492 # Update the commit log message
493 my %info = SVNInfo
( $work_dir );
494 $$msgref .= "\n * $info{URL}: update svn:externals\n";
495 while ( my ( $old, $new ) = each( %changed ) )
497 $$msgref .= " from '$old' to '$new'\n";
498 info
( " '$old' => '$new'\n" );
501 # And set the new externals
502 my ($handle, $tmpfile) = tempfile
( DIR
=> $temp_dir );
503 print $handle join( "\n", @new_externals );
505 SVNCall
( "propset", "--file", $tmpfile, "svn:externals", $work_dir );
510 #------------------------------------------------------------------------------
511 # Function: GetRecursiveExternals
513 # This function retrieves the svn:externals value from the
514 # specified URL or location and subdirectories.
517 # location location of SVN object - file/dir or URL.
520 #------------------------------------------------------------------------------
521 sub GetRecursiveExternals
523 my ( $location ) = @_;
528 my ( $status, @externals ) = SVNCall
( "propget", "-R", "svn:externals", $location );
530 foreach my $external ( @externals )
534 if ( $external =~ m
"(.*) - (.*\s.*)" )
540 push( @
{$retval{$subdir}}, $external ) unless $external =~ m
"^\s*$";
547 #------------------------------------------------------------------------------
550 # Gets the info about the given file.
553 # file The SVN object to query
555 # Returns: hash with the info
556 #------------------------------------------------------------------------------
560 my $old_verbose = $verbose;
562 my ( $retval, @output ) = SVNCall
( "info", $file );
563 $verbose = $old_verbose;
566 return if ( 0 != $retval );
568 foreach my $line ( @output )
570 if ( $line =~ "^(.*): (.*)" )
580 #------------------------------------------------------------------------------
581 # Function: LatestRevision
583 # Returns the repository revision of the last change to the given object not
584 # later than the given revision (i.e. it may return revision, but won't
585 # return revision+1).
588 # source The URL to check
589 # revision The revision of the URL to check from (if not supplied
590 # defaults to last revision).
592 # Returns: The relevant revision number
593 #------------------------------------------------------------------------------
596 my ( $source, $revision ) = @_;
601 $revtext = "--revision $revision:0";
604 my $old_verbose = $verbose;
606 my ( $retval, @output ) = SVNCall
( "log -q", $revtext, $source );
607 $verbose = $old_verbose;
611 error
( "LatestRevision: log -q on '$source' failed" );
616 # The second line should give us the info we need: e.g.
618 # >svn log -q http://subversion/svn/scratch/ianb/svncopy-update/source/dirA
619 # ------------------------------------------------------------------------
620 # r1429 | ib | 2004-06-14 17:39:36 +0100 (Mon, 14 Jun 2004)
621 # ------------------------------------------------------------------------
622 # r1423 | ib | 2004-06-14 17:39:26 +0100 (Mon, 14 Jun 2004)
623 # ------------------------------------------------------------------------
624 # r1422 | ib | 2004-06-14 17:39:23 +0100 (Mon, 14 Jun 2004)
625 # ------------------------------------------------------------------------
626 # r1421 | ib | 2004-06-14 17:39:22 +0100 (Mon, 14 Jun 2004)
627 # ------------------------------------------------------------------------
629 # The second line starts with the latest revision number.
631 if ( $output[1] =~ m
"^r(\d+) \|" )
636 error
( "LatestRevision: log output not formatted as expected\n" );
642 #------------------------------------------------------------------------------
645 # svn commits the temporary directory.
648 # work_dir The working directory
649 # message Commit message
651 # Returns: non-zero on success
652 #------------------------------------------------------------------------------
655 my ( $work_dir, $message ) = @_;
656 my @commandline = ();
658 # Prepare a file containing the message
659 my ($handle, $messagefile) = tempfile
( DIR
=> $temp_dir );
660 print $handle $message;
662 push( @commandline, "--file", $messagefile );
664 push( @commandline, $work_dir );
666 my ( $exit ) = SVNCall
( "commit", @commandline );
668 error
( "$0: svn commit failed" ) if ( 0 != $exit );
670 return ( 0 == $exit );
674 #------------------------------------------------------------------------------
677 # Makes a call to subversion.
680 # command Subversion command
681 # options Other options to pass to Subversion
683 # Returns: exit status, output from command
684 #------------------------------------------------------------------------------
687 my ( $command, @options ) = @_;
689 my @commandline = ( $svn, $command, @svn_options, @options );
691 info
( " > ", join( " ", @commandline ), "\n" );
693 my @output = qx( @commandline 2>&1 );
696 my $exit = $result >> 8;
697 my $signal = $result & 127;
698 my $cd = $result & 128 ?
"with core dump" : "";
701 error
( "$0: 'svn $command' failed $cd: exit=$exit signal=$signal\n" );
706 info
( join( "\n", @output ) );
710 return ( $exit, @output );
717 #------------------------------------------------------------------------------
718 # Function: FindRepositoryRoot
720 # Returns the root of the repository for a given URL. Do
721 # this with the svn log command. Take the svn_url hostname and port
722 # as the initial url and append to it successive portions of the final
723 # path until svn log succeeds.
726 # URI URI within repository
728 # Returns: A URI for the root, or undefined on error
729 #------------------------------------------------------------------------------
730 sub FindRepositoryRoot
734 my $repos_root_uri_path;
735 my $old_verbose = $verbose;
738 info
( "Finding the root URL of '$URI'.\n" );
741 my @path_segments = grep { length($_) } $r->path_segments;
742 unshift(@path_segments, '');
746 while (@path_segments)
748 $repos_root_uri_path = shift @path_segments;
749 push(@r_path_segments, $repos_root_uri_path);
750 $r->path_segments(@r_path_segments);
751 if ( SVNCall
( 'log', '-r', 'HEAD', $r ) == 0 )
753 $repos_root_uri = $r;
758 $verbose = $old_verbose;
762 info
( "Determined that the svn root URL is $repos_root_uri.\n\n" );
763 return $repos_root_uri;
767 error
( "$0: cannot determine root svn URL for '$URI'.\n" );
773 #------------------------------------------------------------------------------
774 # Function: CreateSVNDirectories
776 # Creates a directory in Subversion, including all intermediate directories.
779 # URI directory path to create.
780 # message commit message (optional).
782 # Returns: 1 on success, 0 on error
783 #------------------------------------------------------------------------------
784 sub CreateSVNDirectories
786 my ( $URI, $message ) = @_;
788 my @path_segments = grep { length($_) } $r->path_segments;
790 unshift(@path_segments, '');
796 # Prepare a file containing the message
797 my ($handle, $messagefile) = tempfile
( DIR
=> $temp_dir );
798 print $handle $message;
800 my @msgcmd = ( "--file", $messagefile );
802 # We're going to get errors while we do this. Don't show the user.
803 my $old_verbose = $verbose;
805 # Find the repository root
806 while (@path_segments)
808 my $segment = shift @path_segments;
809 push( @r_path_segments, $segment );
810 $r->path_segments( @r_path_segments );
813 if ( SVNCall
( 'log', '-r', 'HEAD', $r ) == 0 )
815 # We've found the root of the repository.
819 elsif ( !$found_tail )
821 if ( SVNCall
( 'log', '-r', 'HEAD', $r ) != 0 )
823 # We've found the first directory which doesn't exist.
830 # We're creating directories
831 $verbose = $old_verbose;
832 if ( 0 != SVNCall
( 'mkdir', @msgcmd, $r ) )
834 error
( "Couldn't create directory '$r'" );
839 $verbose = $old_verbose;
845 #------------------------------------------------------------------------------
848 # Prints out an informational message in verbose mode
851 # @_ The message(s) to print
854 #------------------------------------------------------------------------------
864 #------------------------------------------------------------------------------
867 # Prints out and logs an error message
870 # @_ The error messages
873 #------------------------------------------------------------------------------
878 # This is used during testing
884 # Now print out each error message and add it to the list.
885 foreach $error ( @_ )
887 my $text = "svncopy.pl: $error\n";
888 push( @errors, $text );
897 #------------------------------------------------------------------------------
900 # Prints out usage information.
903 # optional error message
906 #------------------------------------------------------------------------------
910 $msg = "\n*** $_[0] ***\n" if $_[0];
912 pod2usage
( { -message
=> $msg,
917 #------------------------------------------------------------------------------
918 # This package exists just to delete the temporary directory.
919 #------------------------------------------------------------------------------
920 package Temp
::Delete
;
922 use File
::Temp
0.12 qw(tempdir);
927 my $class = ref($this) || $this;
931 my $temp_dir = tempdir
("svncopy_XXXXXXXXXX", TMPDIR
=> 1);
933 $self->{tempdir
} = $temp_dir;
941 return $self->{tempdir
};
947 my $temp_dir = $self->{tempdir
};
948 if ( scalar( @errors ) )
950 print "Leaving $temp_dir for inspection\n";
954 info
( "Cleaning up $temp_dir\n" );
955 File
::Path
::rmtree
([$temp_dir], 0, 0);
960 #------------------------------------------------------------------------------
961 # Documentation follows, in pod format.
962 #------------------------------------------------------------------------------
967 B<svncopy> - extended form of B<svn copy>
971 B<svncopy.pl> [option ...] source [source ...] destination
973 This script copies one Subversion location or set of locations to another,
974 in the same way as B<svn copy>. Using the script allows more advanced operations,
975 in particular allowing svn:externals to be dealt with properly for branching
979 source Subversion item to copy from.
980 Multiple sources can be given.
981 destination Destination to copy to.
984 -t [--tag] : set svn:externals to current version
986 -b [--branch] : update fully contained svn:externals
988 -m [--message] arg : specify commit message ARG
989 -F [--file] arg : read data from file ARG
990 -r [--revision] arg : ARG (some commands also take ARG1:ARG2 range)
991 A revision argument can be one of:
992 NUMBER revision number
993 "{" DATE "}" revision at start of the date
994 "HEAD" latest in repository
995 "BASE" base rev of item's working copy
996 "COMMITTED" last commit at or before BASE
997 "PREV" revision just before COMMITTED
998 -q [--quiet] : print as little as possible
999 --username arg : specify a username ARG
1000 --password arg : specify a password ARG
1001 --no-auth-cache : do not cache authentication tokens
1002 --force-log : force validity of log message source
1003 --encoding arg : treat value as being in charset encoding ARG
1004 --config-dir arg : read user config files from directory ARG
1005 --[no]verbose : sets the script to give lots of output
1013 The subversion item or items to copy from.
1015 =item B<destination>
1017 The destination URL to copy to.
1025 =item B<-t [--pin-externals or --tag]>
1027 Update any svn:externals to ensure they have a version number,
1028 using the current destination version if none is already specified.
1029 Useful for tagging operations.
1031 =item B<-b [--update-externals or --branch]>
1033 Update any unversioned svn:externals which point to a location
1034 within one of the sources so that they point to the corresponding
1035 location within the destination.
1037 Note: --pin-externals and --update-externals are mutually exclusive.
1039 =item B<-m [--message] arg>
1041 Specify commit message ARG
1043 =item B<-F [--file] arg>
1045 Read data from file ARG
1047 =item B<-r [--revision] arg>
1049 ARG (some commands also take ARG1:ARG2 range)
1050 A revision argument can be one of:
1052 NUMBER revision number
1053 "{" DATE "}" revision at start of the date
1054 "HEAD" latest in repository
1055 "BASE" base rev of item's working copy
1056 "COMMITTED" last commit at or before BASE
1057 "PREV" revision just before COMMITTED
1059 =item B<-q [--quiet]>
1061 Print as little as possible
1063 =item B<--username arg>
1065 Specify a username ARG
1067 =item B<--password arg>
1069 Specify a password ARG
1071 =item B<--no-auth-cache>
1073 Do not cache authentication tokens
1075 =item B<--force-log>
1077 Force validity of log message source
1079 =item B<--encoding arg>
1081 Treat value as being in charset encoding ARG
1083 =item B<--config-dir arg>
1085 Read user configuration files from directory ARG
1087 =item B<--[no]verbose>
1089 Sets the script to give lots of output when it runs.
1093 Print a brief help message and exits.
1099 This script performs an B<svn copy> command. It allows extra processing to get
1100 around the following limitations of B<svn copy>:
1102 svn:externals definitions are (in Subversion 1.0 and 1.1 at least) absolute paths.
1103 This means that an B<svn copy> used as a branch or tag operation on a tree with
1104 embedded svn:externals will not do what is expected. The svn:externals
1105 will still point at the original location and will not be pinned down.
1107 B<svncopy --update-externals> (or B<svncopy --branch>) will update any
1108 unversioned svn:externals in the destination tree which point at locations
1109 within one of the source trees so that they point to the corresponding locations
1110 within the destination tree instead. This effectively updates the reference to
1111 point to the destination tree, and is the behaviour you want for branching.
1113 B<svncopy --pin-externals> (or B<svncopy --tag>) will update any unversioned
1114 svn:externals in the destination tree to contain the current version of the
1115 directory listed in the svn:externals definition. This effectively pins
1116 the reference to the current version, and is the behaviour you want for tagging.
1118 Note: both forms of the command leave unchanged any svn:externals which
1119 already contain a version number.
1123 #------------------------------- END OF FILE ----------------------------------