The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
Introduction
============

This Perl script copies one Subversion location or set of locations to another,
in the same way as svn copy.  Using the script allows more advanced operations,
in particular allowing svn:externals to be dealt with properly for branching
or tagging.

Command Line Options
====================

Run the script with no command line arguments to see all the command
line options it takes.

Dependencies
============

This script depends on module File::Temp.  This became part of the standard
Perl distribution in 5.8.0.  If you have an earlier version of Perl, you can
download it from CPAN at http://search.cpan.org/search?module=File::Temp .
It works with Perl versions from 5.005 onwards.

Overview
========

This script performs an svn copy command.  It allows extra processing to get
around the following limitations of svn copy:

  svn:externals definitions are (in Subversion 1.0 and 1.1 at least) absolute
  paths.  This means that an svn copy used as a branch or tag operation on a
  tree with embedded svn:externals will not do what is expected.  The
  svn:externals will still point at the original location and will not be
  pinned down.

Installation
============

Rename svncopy.pl.in to svncopy.pl.

Locate the following lines in the script:

    # Specify the location of the svn command.
    my $svn = '@SVN_BINDIR@/svn';

Replace @SVN_BINDIR@ with the path to your svn executable.  This should be the
command you have to type to use the svn command line.  If svn is on the path,
you can just set this line to:

    my $svn = 'svn';

Alternatively, if you have to type /usr/local/bin/svn, you should set it to:

    my $svn = '/usr/local/bin/svn';

Branching
=========

svncopy --update-externals (or svncopy --branch) will update any unversioned
svn:externals in the destination tree which point at locations within one of
the source trees so that they point to the corresponding locations within the
destination tree instead.  This effectively updates the reference to
point to the destination tree, and is the behaviour you want for branching.

Tagging
=======

svncopy --pin-externals (or svncopy --tag) will update any unversioned
svn:externals in the destination tree to contain the current version of the
directory listed in the svn:externals definition.  This effectively pins
the reference to the current version, and is the behaviour you want for tagging.

Note: both forms of the command leave unchanged any svn:externals which
already contain a version number.

Examples
========

These examples assume the following repository layout:

Path                Last mod or svn:externals target
----                --------------------------------
trunk/              5195
    common/         5192
        common1.c   5192
        common2.c   4997
    inc/            4986
        common1.h   4331
        common2.h   4986
    proj_foo/       5003
        foo1.c      5001
        foo2.c      4995
        X common    -r 4997 http://svn/repos/trunk/common
        X inc       http://svn/repos/trunk/inc
    proj_bar/       5195
        bar1.c      5054
        bar2.c      5195
        bar2.h      5195
        X common    http://svn/repos/trunk/common
        X inc       http://svn/repos/trunk/inc
        X public    http://someserver/repos/public
        
i.e. both proj_foo and proj_bar have svn:externals set to:

common    http://svn/repos/trunk/common
inc       http://svn/repos/trunk/inc

with proj_foo having pinned common to version 4997.

Example 1 - using svn copy to tag (what not to do)
--------------------------------------------------

This is the naive way of creating a tag.

$ svn copy http://svn/repos/trunk/proj_bar \
            http://svn/repos/tags/proj_bar/release_3.2

Result:

trunk/
    [ as above]
tags/
    proj_bar/
        release_3.2/
            bar1.c
            bar2.c
            bar2.h
            X common    http://svn/repos/trunk/common
            X inc       http://svn/repos/trunk/inc
            X public    http://someserver/repos/public

The svn:externals are still pointing to the head revisions in trunk.  Any
changes in trunk/common, trunk/inc or trunk/project/inc will modify the
subdirectories in tags/proj_bar/release_3.2.  This is not the desired effect.

Example 2 - using svn copy to branch (what not to do)
-----------------------------------------------------

This is the naive way of creating a branch.

$ svn copy http://svn/repos/trunk/proj_bar \
            http://svn/repos/branches/proj_bar/3.2_bugfix

Result:

trunk/
    [ as above]
branches/
    proj_bar/
        3.2_bugfix/
            proj_bar/
                bar1.c
                bar2.c
                bar2.h
                X common    http://svn/repos/trunk/common
                X inc       http://svn/repos/trunk/inc
                X public    http://someserver/repos/public

The svn:externals are still pointing to the head revisions in trunk.  Any
changes in trunk/common, trunk/inc or trunk/project/inc will modify the
subdirectories in branches/proj_bar/3.2_bugfix/proj_bar.  Worse, any
changes in these subdirectories will get propagated back to trunk.  Again,
this is not the desired effect.

Example 3 - tagging properly
----------------------------

Using the script allows tags to be created which won't change.

$ perl svncopy.pl --tag http://svn/repos/trunk/proj_bar \
            http://svn/repos/tags/proj_bar/release_3.2

Result:

trunk/
    [ as above]
tags/
    proj_bar/
        release_3.2/
            proj_bar/
                bar1.c
                bar2.c
                bar2.h
                X common    -r 5192 http://svn/repos/trunk/common
                X inc       -r 4986 http://svn/repos/trunk/inc
                X public    -r 17753 http://someserver/repos/public

The svn:externals are pinned to the latest repository version containing a
modification to the corresponding directories.  The contents of the externals
will not change.

Example 4 - tagging retrospectively
-----------------------------------

If you want to create a tag, but changes have been made since the version you
want to tag, all is not lost.  Pass the revision number and the tag will be
done from there.  E.g. if proj_foo should have been tagged at version 5001
(when common was at 4997), the following command will do the trick:

$ perl svncopy.pl --tag --revision 5001 http://svn/repos/trunk/proj_foo \
            http://svn/repos/tags/proj_foo/release_2.7

Result:

trunk/
    [ as above]
tags/
    proj_foo/
        release_2.7/
            proj_foo/
                foo1.c
                foo2.c
                X common    -r 4997 http://svn/repos/trunk/common
                X inc       -r 4986 http://svn/repos/trunk/inc

The svn:externals are pinned to the latest repository version containing a
modification to the corresponding directories prior to revision 5002.
The contents of the externals will not change.

Example 5 - branching properly
------------------------------

Using the script allows branches to be created which are really independent.

$ perl svncopy.pl --branch http://svn/repos/trunk \
            http://svn/repos/branches/3.2_bugfix

Result:

trunk/
    [ as above]
branches/
    3.2_bugfix/
        trunk/
            common/
                common1.c
                common2.c
            inc/
                common1.h
                common2.h
            proj_foo/
                foo1.c
                foo2.c
                X common    -r 4997 http://svn/repos/trunk/common
                X inc       http://svn/repos/branches/3.2_bugfix/trunk/inc
            proj_bar/
                bar1.c
                bar2.c
                bar2.h
                X common    http://svn/repos/branches/3.2_bugfix/trunk/common
                X inc       http://svn/repos/branches/3.2_bugfix/trunk/inc
                X public    http://someserver/repos/public

The svn:externals are now pointing to the corresponding directories in the
branch.  The subdirectories in the branch will be unaffected by changes
in trunk, and similarly trunk will not be affected by changes in the branch.

Note: proj_foo/common was pinned to revision 4997 in trunk.  Because of this
the script has left it unchanged.

Example 6 - branching part of a tree
------------------------------------

If you don't want to branch the whole tree, you can just branch the directories
which contain your project:

$ perl svncopy.pl --branch http://svn/repos/trunk/common \
            http://svn/repos/trunk/inc \
            http://svn/repos/trunk/proj_bar \
            http://svn/repos/branches/3.2_bugfix

Result:

trunk/
    [ as above]
branches/
    3.2_bugfix/
        common/
            common1.c
            common2.c
        inc/
            common1.h
            common2.h
        proj_bar/
            bar1.c
            bar2.c
            bar2.h
            X common    http://svn/repos/branches/3.2_bugfix/common
            X inc       http://svn/repos/branches/3.2_bugfix/inc
            X public    http://someserver/repos/public

The svn:externals are now pointing to the corresponding directories in
the branch, as in Example 4.

Note: you *must* branch all affected directories simultaneously.  If you
branch them one at a time, the script will not know which externals refer
to other components of the same project, and will leave them unchanged.

Testing svncopy.pl
==================

svncopy.pl comes with a script to do some basic testing, called testsvncopy.pl.
Installation is similar to svncopy.pl - update @SVN_BINDIR@.  You also have to
supply a scratch repository location for the test script to use.  Either update
@SVN_TEST_REPOSITORY@ in testsvncopy.pl.in or pass in the location using the
--test-repository parameter when running the script.