Subversion Repositories DevTools

Rev

Rev 387 | Rev 1403 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | RSS feed

########################################################################
# Copyright (C) 1998-2008 ERG Limited, All rights reserved
#
# Module name   : jats_svn.pl
# Module type   : Jats Utility
# Compiler(s)   : Perl
# Environment(s): Jats
#
# Description   : A script to perform a number of SVN utility functions
#                 The script will:
#                   Delete a package
#                   Create a package
#                   Import source to a package
#
#
#......................................................................#

require 5.006_001;
use strict;
use warnings;
use JatsError;
use JatsSvn qw(:All);
use JatsLocateFiles;
use JatsProperties;


use Pod::Usage;                                 # required for help support
use Getopt::Long qw(:config require_order);     # Stop on non-option
use Cwd;
use File::Path;
use File::Copy;
use File::Basename;
use File::Compare;
use Encode;

my $VERSION = "1.0.0";                          # Update this

#
#   Options
#
my $opt_debug   = $ENV{'GBE_DEBUG'};            # Allow global debug
my $opt_verbose = $ENV{'GBE_VERBOSE'};          # Allow global verbose
my $opt_help = 0;

#
#   Globals
#
my $opr_done;                                   # User has done something

#-------------------------------------------------------------------------------
# Function        : Mainline Entry Point
#
# Description     :
#
# Inputs          :
#
my $result = GetOptions (
                "help:+"        => \$opt_help,              # flag, multiple use allowed
                "manual:3"      => \$opt_help,              # flag
                "verbose:+"     => \$opt_verbose,           # flag, multiple use allowed

                );

                #
                #   UPDATE THE DOCUMENTATION AT THE END OF THIS FILE !!!
                #

#
#   Process help and manual options
#
pod2usage(-verbose => 0, -message => "Version: $VERSION") if ($opt_help == 1 || ! $result);
pod2usage(-verbose => 1) if ($opt_help == 2 );
pod2usage(-verbose => 2) if ($opt_help > 2);

#
#   Configure the error reporting process now that we have the user options
#
ErrorConfig( 'name'    =>'SVN',
             'verbose' => $opt_verbose,
            );

#
#   Reconfigure the options parser to allow subcommands to parse options
#
Getopt::Long::Configure('permute');

#
#   Process command
#   First command line argument is a subversion command
#
my $cmd = shift @ARGV || "help";
CreatePackage()                        if ( $cmd =~ m/^create/ );
DeleteBranch()                         if ( $cmd =~ m/^delete-branch/ );
DeletePackage()                        if ( $cmd =~ m/^delete-package/ );
ImportPackage()                        if ( $cmd =~ m/^import/ );
SvnRepoCmd($cmd, @ARGV)                if ( $cmd eq 'ls' );
TestSvn()                              if ($cmd eq 'test');
ShowPaths()                            if ( $cmd =~ m/^path/ );
ShowTag()                              if ( $cmd =~ m/^tag/ );
ShowUrl()                              if ( $cmd =~ m/^url/ );

pod2usage(-verbose => 0, -message => "No valid operations specified") unless ( $opr_done );
exit 0;

#-------------------------------------------------------------------------------
# Function        : ShowPaths
#
# Description     : Show PATHS
#
# Inputs          : 
#
# Returns         : 
#
sub ShowPaths
{
    #
    #   Parse more options
    #
    GetOptions (
                "help:+"        => \$opt_help,
                "manual:3"      => \$opt_help,
                ) || Error ("Invalid command line" );

    #
    #   Subcommand specific help
    #
    SubCommandHelp( $opt_help, "Subversion Paths") if ($opt_help || $#ARGV >= 0);

    #
    #   Display known PATHS
    #
    my ( $pSVN_URLS, $pSVN_URLS_LIST) = SvnPaths();
    print ("Configured SubVersion Repository Paths\n");
    print sprintf("    %-20s %s\n", 'Tag', 'URL Prefix');

    foreach my $key ( @{$pSVN_URLS_LIST} )
    {
        print sprintf("    %-20s %s\n", $key || 'Default', $pSVN_URLS->{$key} );
    }

    $opr_done = 1;
}

#-------------------------------------------------------------------------------
# Function        : ShowTag
#
# Description     : Convert a URL into a TAG
#                   Convert the current workspace info into a TAG
#
# Inputs          : url                     - Url to convert (optional)
#                   Options
#                       -help[=n]           - Show help
#                       -man                - Show manual
#                       -url=url            - Convert URL
#                       -path=path          - Convert Workspace
#
# Returns         : Nothing
#
sub ShowTag
{
    my $opt_path;
    my $opt_url;

    #
    #   Parse more options
    #
    GetOptions (
                "help:+"        => \$opt_help,
                "manual:3"      => \$opt_help,
                "path:s"        => \$opt_path,
                "url:s"         => \$opt_url,
                ) || Error ("Invalid command line" );

    #
    #   Subcommand specific help
    #
    SubCommandHelp( $opt_help, "Url to Tag") if ($opt_help);

    #
    #   Bare argument is a URL
    #   If no arguments provided assume a path of the current directory
    #
    $opt_url = shift (@ARGV) if ( $#ARGV == 0 );
    $opt_path = '.' unless ( defined $opt_path || defined $opt_url );

    #
    #   Sanity Tests
    #
    Error ("Cannot specify both a URL and a PATH")
            if ( defined $opt_url && defined $opt_path );
    Warning ("Too many arguments") if ( $#ARGV >= 0 );

    #   Do all the hard work
    #
    my $uref;
    if ( $opt_url )
    {
        $uref = NewSessionByUrl ( $opt_url );
        $uref->CalcRmReference($uref->Full());
    }
    else
    {
        $uref = NewSessionByWS($opt_path, 0, 1);
        my $ws_root = $uref->SvnLocateWsRoot(1);
        $uref->CalcRmReference($uref->FullWs());
    }

    Message ("Tag is: " . $uref->RmRef() );
    $opr_done = 1;
}
#-------------------------------------------------------------------------------
# Function        : ShowUrl
#
# Description     : Convert a TAG into a URL
#                   Show the current workspace URL
#
# Inputs          : tag                     - Tag to convert (optional)
#                   Options
#                       -help[=n]           - Show help
#                       -man                - Show manual
#                       -tag=tag            - Convert TAG
#                       -path=path          - Convert Workspace
#
# Returns         : Nothing
#
sub ShowUrl
{
    my $opt_path;
    my $opt_tag;

    #
    #   Parse more options
    #
    GetOptions (
                "help:+"        => \$opt_help,
                "manual:3"      => \$opt_help,
                "path:s"        => \$opt_path,
                "tag:s"         => \$opt_tag,
                ) || Error ("Invalid command line" );

    #
    #   Subcommand specific help
    #
    SubCommandHelp( $opt_help, "Tag to Url") if ($opt_help);

    #
    #   Bare argument is a TAG
    #   If no arguments provided assume a path of the current directory
    #
    $opt_tag = shift (@ARGV) if ( $#ARGV == 0 );
    $opt_path = '.' unless ( defined $opt_path || defined $opt_tag );

    #
    #   Sanity Tests
    #
    Error ("Cannot specify both a TAG and a PATH")
            if ( defined $opt_tag && defined $opt_path );
    Warning ("Too many arguments") if ( $#ARGV >= 0 );

    #   Do all the hard work
    #
    my $uref;
    my $url;
    if ( $opt_tag )
    {
        $url = SvnPath2Url($opt_tag);
    }
    else
    {
        $uref = NewSessionByWS($opt_path, 0, 1);
        my $ws_root = $uref->SvnLocateWsRoot(1);
        $url = $uref->FullWsRev();
    }

    Message ("Url: $url");
    $opr_done = 1;
}

#-------------------------------------------------------------------------------
# Function        : TestSvn
#
# Description     : Test access to subversion
#
# Inputs          : None
#
# Returns         :
#
sub TestSvn
{
    #
    #   Parse more options
    #
    GetOptions (
                "help:+"        => \$opt_help,
                "manual:3"      => \$opt_help,
                ) || Error ("Invalid command line" );

    #
    #   Subcommand specific help
    #
    SubCommandHelp( $opt_help, "Test Subversion") if ($opt_help || $#ARGV >= 0);

    SvnUserCmd( '--version');
    $opr_done = 1;
}


#-------------------------------------------------------------------------------
# Function        : SvnRepoCmd
#
# Description     : Execute a SVN command, where the first argument
#                   is a repository specifier
#
# Inputs          : $cmd
#                   $repo_url
#                   @opts
#
# Returns         : 
#
sub SvnRepoCmd
{
    my ( $cmd, $repo_url, @opts ) = @_;
    my $uref = NewSessionByUrl ( $repo_url );

    SvnUserCmd( $cmd,
            $uref->Full,
            @opts,
            { 'credentials' => 1 });
            
    $opr_done = 1;
}

#-------------------------------------------------------------------------------
# Function        : DeletePackage
#
# Description     : Delete a Package structure within a Repository
#                   Intended for test usage
#
# Inputs          : URL                 - Url to Repo + Package Base
#
# Returns         : 
#
sub DeletePackage
{
    my $opt_error = 0;
    #
    #   Parse more options
    #
    GetOptions (
                "help:+"        => \$opt_help,
                "manual:3"      => \$opt_help,
                "error!"       => \$opt_error,
                ) || Error ("Invalid command line" );

    #
    #   Subcommand specific help
    #
    SubCommandHelp( $opt_help, "Delete a Package") if ($opt_help || $#ARGV < 0);

    #
    #   Sanity Tests
    #
    Message ("Delete Entire Package Tree" );
    Warning ("Too many arguments: @ARGV") if ( $#ARGV >= 1 );

    #
    #   Do all the hard work
    #       Create
    #       Import
    #       Label
    #
    my $uref = NewSessionByUrl ( $ARGV[0] );
    $uref->SvnValidatePackageRoot(!$opt_error);
    $uref->SvnDelete (
                      'target'      => $uref->Full,
                      'comment'   => [$uref->Path().": Delete Package",'Deleted by user command: jats svn delete-package'],
                      'noerror'   => !$opt_error,
                      );
    $opr_done = 1;
}

#-------------------------------------------------------------------------------
# Function        : CreatePackage
#
# Description     : Create a Package structure within a Repository
#                   Optionally Import Data
#                   Optionally Tag the import
#                   Optionally Tag the import on a branch
#
# Inputs          : URL                 - Url to Repo + Package Base
#                   Options             - Command modifiers
#                       -import=path    - Import named directory
#                       -label=name     - Label the result
#                       -tag=name       - Import to Tag Only
#                       -branch=name    - Import to Branch only
#                       -new            - Must be new package
#                       -replace        - Replace existing
#
# Returns         : 
#
sub CreatePackage
{
    my $opt_import;
    my $opt_tag;
    my $opt_branch;
    my $opt_trunk;
    my $opt_new;
    my $opt_label;
    my $opt_replace;
    my $pname;
    my $type;
    my $opt_author;
    my $opt_date;


    Message ("Create New Package Version" );

    #
    #   Parse more options
    #
    GetOptions (
                "help:+"        => \$opt_help,
                "manual:3"      => \$opt_help,
                "verbose:+"     => \$opt_verbose,
                "import=s"      => \$opt_import,
                "new"           => \$opt_new,
                "branch=s"      => \$opt_branch,
                "trunk"         => \$opt_trunk,
                "tag=s"         => \$opt_tag,
                "label=s"       => \$opt_label,
                "replace"       => \$opt_replace,
                'author=s'      => \$opt_author,
                'date=s'        => \$opt_date,

                ) || Error ("Invalid command line" );

    #
    #   Subcommand specific help
    #
    SubCommandHelp( $opt_help, "Create a Package Version") if ($opt_help || $#ARGV < 0);
                
    #
    #   Alter the error reporting parameters
    #
    ErrorConfig( 'verbose' => $opt_verbose );

    #
    #   Sanity Tests
    #
    my $count = 0;
    $count++ if ( $opt_trunk );
    $count++ if ( $opt_branch );
    $count++ if ( $opt_tag );
    Error ("Conflicting options: -trunk, -tag, -branch") if ( $count > 1 );
    Error ("Nothing imported to be labeled") if ( $count && !$opt_import );
    Error ("Import path does not exist: $opt_import") if ( $opt_import && ! -d $opt_import );
    Error ("Conflicting options: new and replace") if ( $opt_new && $opt_replace );
    Error ("Too many command line arguments") if ( exists $ARGV[1] );

    ($type, $opt_label) = ('tags', $opt_tag)            if ( $opt_tag);
    ($type, $opt_label) = ('branches', $opt_branch)     if ( $opt_branch );
    ($type, $opt_label) = ('trunk', $opt_label)         if ( $opt_trunk);

    #
    #   Do all the hard work
    #       Create
    #       Import
    #       Label
    #
    my $uref = NewSessionByUrl ( $ARGV[0] );
    $uref->SvnCreatePackage (
                      'import'  => $opt_import,
                      'label'   => $opt_label,
                      'type'    => $type,
                      'new'     => $opt_new,
                      'replace' => $opt_replace,
                      );
    #
    # Report RmPath as using a pegged version of a new package is a bit silly
    #
    Message ("Repository Ref: " . $uref->RmPath);
    if ( $uref->{REVNO} )
    {
        $uref->setRepoProperty('svn:author', $opt_author) if (defined ($opt_author));
        $uref->setRepoProperty('svn:date', $opt_date) if (defined ($opt_date));
    }
    $opr_done = 1;
}

#-------------------------------------------------------------------------------
# Function        : ImportPackage
#
# Description     : Import a new version of a package
#                   Take great care to reuse file-versions that are already in
#                   the  package
#
#                   Intended to allow the importation of multiple
#                   versions of a package
#
# Inputs          : 
#
# Returns         : 
#
sub ImportPackage
{
    Message ("Import Package Version" );

    #
    #   Options
    #
    my $opt_package;
    my $opt_dir;
    my $opt_label;
    my $opt_replace = 0;
    my $opt_reuse;
    my $opt_workdir = "SvnImportDir";
    my $opt_delete = 1;
    my $opt_author;
    my $opt_date;
    my $opt_log = '';
    my $opt_branch;
    my $opt_datafile;

    #
    #   Other globals
    #
    my $url_label;
    my $url_branch;

    #
    #   Configuration options
    #
    my $result = GetOptions (
                    'help:+'        => \$opt_help,
                    'manual:3'      => \$opt_help,
                    'verbose:+'     => \$opt_verbose,
                    'package=s'     => \$opt_package,
                    'dir=s'         => \$opt_dir,
                    'label=s'       => \$opt_label,
                    'branch=s'      => \$opt_branch,
                    'replace'       => \$opt_replace,
                    'reuse'         => \$opt_reuse,
                    'workspace=s'   => \$opt_workdir,
                    'delete!'       => \$opt_delete,
                    'author=s'      => \$opt_author,
                    'date=s'        => \$opt_date,
                    'log=s'         => \$opt_log,
                    'datafile=s'    => \$opt_datafile,

                    #
                    #   Update documentation at the end of the file
                    #
                    ) || Error ("Invalid command line" );

    #
    #   Insert defaults
    #   User can specify base package via -package or a non-options argument
    #
    $opt_package = $ARGV[0] unless ( $opt_package );
    unlink $opt_datafile if ( defined $opt_datafile );
                    
    #
    #   Subcommand specific help
    #
    SubCommandHelp( $opt_help, "Import directory to a Package")
        if ($opt_help || ! $opt_package );

    #
    #   Alter the error reporting parameters
    #
    ErrorConfig( 'verbose' => $opt_verbose );

    #
    #   Configure the error reporting process now that we have the user options
    #
    Error ("No package URL specified") unless ( $opt_package );
    Error ("No base directory specified") unless ( $opt_dir );
    Error ("Invalid base directory: $opt_dir") unless ( -d $opt_dir );

    #
    #   Create an SVN session
    #
    my $svn = NewSessionByUrl ( $opt_package );

    #
    #   Ensure that the required label is available
    #
    if ( $opt_label )
    {
        $opt_label = SvnIsaSimpleLabel ($opt_label);
        $url_label = $svn->BranchName( $opt_label, 'tags' );
        $svn->SvnValidateTarget (
                        'target' => $url_label,
                        'available' => 1,
                        ) unless ( $opt_replace );
    }

    #
    #   Validate the required branch
    #   It will be created if it doesn't exist
    #
    if ( $opt_branch )
    {
        $opt_branch = SvnIsaSimpleLabel($opt_branch);
        $url_branch = $svn->BranchName( $opt_branch, 'branches' );
        my $rv = $svn->SvnValidateTarget (
                        'cmd'    => 'SvnImporter. Create branch',
                        'target' => $url_branch,
                        'create' => 1,
                        );
        if ( $rv == 2 )
        {
            $svn->setRepoProperty('svn:author', $opt_author) if (defined ($opt_author));
            $svn->setRepoProperty('svn:date', $opt_date) if (defined ($opt_date));
        }
    }

    #
    #   Create a workspace based on the users package
    #   Allow the workspace to be reused to speed up multiple
    #   operations
    #
    unless ( $opt_reuse && -d $opt_workdir )
    {
        Message ("Creating Workspace");
        rmtree( $opt_workdir );

        $svn->SvnValidatePackageRoot ();
        #DebugDumpData( 'Svn', $svn );
        $svn->SvnValidateTarget (
                            'cmd'    => 'SvnImporter',
                            'target' => $svn->Full,
                            'require' => 1,
                            );

        my $url_co = $opt_branch ? $url_branch : $svn->Full . '/trunk';
        $svn->SvnCo ( $url_co, $opt_workdir, $opt_verbose ? '--Print' : '--NoPrint' );
        Error ("Cannot locate the created Workspace")
            unless ( -d $opt_workdir );
    }
    else
    {
        Message ("Reusing Workspace");
    }
                    
    #
    #   Determine differences between the two folders
    #       Create structures for each directory
    #
    Message ("Determine Files in packages");

    my $search = JatsLocateFiles->new("--Recurse=1",
                                       "--DirsToo",
                                       "--FilterOutRe=/\.svn/",
                                       "--FilterOutRe=/\.svn\$",
                                       "--FilterOutRe=^/${opt_workdir}\$",
                                       "--FilterOutRe=^/${opt_workdir}/",
                                       );
    my @ws = $search->search($opt_workdir);
    my @dir = $search->search($opt_dir);

    #
    #   Scan for a source file
    #   Trying to detect empty views
    #   Look for file, not directory
    #
    {
        my $fileFound = 0;
        foreach ( @dir )
        {
            next if ( m~/$~ );
            $fileFound++;
            last;
        }

        unless ( $fileFound )
        {
            Warning ("No source files found in source view");
            $opr_done = 1;
            return;
        }
    }

    #Information ("WS Results", @ws);
    #Information ("DIR Results", @dir);
    #Information ("WS Results: ", scalar @ws);
    #Information ("DIR Results:", scalar @dir);

    #
    #   Create a hash the Workspace and the User dir
    #   The key will be file names
    #
    my %ws;  map ( $ws{$_} = 1 , @ws );
    my %dir; map ( $dir{$_} = 1 , @dir );

    #
    #   Create a hash of common elements
    #   Removing then from the other two
    #
    my %common;
    foreach ( keys %ws )
    {
        next unless ( exists $dir{$_} );
        $common{$_} = 1;
        delete $ws{$_};
        delete $dir{$_};
    }

    #DebugDumpData( 'WS', \%ws );
    #DebugDumpData( 'DIR', \%dir );
    #DebugDumpData( 'COMMON', \%common );

    #
    #   Need to consider the case where a file has been replaced with a directory
    #   and visa-versa. Delete files and directories first.
    #
    #
    #   Remove files
    #   Sort in reverse. This will ensure that we process directory
    #   contents before directories
    #
    my @rm_files = reverse sort keys %ws;
    if ( @rm_files )
    {
        foreach my $file ( @rm_files  )
        {
            Verbose ("Removing $file");
            unlink "$opt_workdir/$file";
        }

        #
        #   Inform Subversion about the removed files
        #
        my $base = 0;
        my $num = $#rm_files;
        Message ("Update the workspace: Removed " . ($num + 1) . " Files");

        while ( $base <= $num )
        {
            my $end = $base + 200;
            $end = $num if ( $end > $num );

            $svn->SvnCmd ( 'delete', map ("$opt_workdir/$_@", @rm_files[$base .. $end] ),
                            { 'error' => 'Deleting files from workspace' } );
            
            $base = $end + 1;
        }
    }
    
    #
    #   Add New Files
    #   Won't add empty directories at this point
    #
    #   Process by sorted list
    #   This will ensure we process parent directories first
    #
    my @added = sort keys %dir;
    if ( @added )
    {
        foreach my $file ( @added  )
        {
            my $src = "$opt_dir/$file";
            my $target = "$opt_workdir/$file";

            if ( -d $src )
            {
                Verbose ("Adding directory: $file");
                mkdir ( $target ) unless (-d $target);
            }
            else
            {

                my $path = dirname ( $target);
                mkdir ( $path ) unless (-d $path);

                Verbose ("Adding $file");
                unless (File::Copy::copy( $src, $target ))
                {
                    Error("Failed to transfer file [$file]: $!");
                }
            }
        }

        #
        #   Inform Subversion about the added files
        #   The command line does have a finite length, so add them 200 at a
        #   time.
        #

        my $base = 0;
        my $num = $#added;
        Message ("Update the workspace: Added " . (1 + $num) . " files");

        while ( $base <= $num )
        {
            my $end = $base + 200;
            $end = $num if ( $end > $num );

            $svn->SvnCmd ( 'add'
                            , '--depth=empty'
                            , '--parents'
                            , map ("$opt_workdir/$_@", @added[$base .. $end] ),
                            { 'error' => 'Adding files to workspace' } );

            $base = $end + 1;
        }
    }

    #
    #   The common files may have changed
    #   Simply copy them all in and let subversion figure it out
    #
    foreach my $file ( sort keys %common  )
    {
        my $src = "$opt_dir/$file";
        my $target = "$opt_workdir/$file";

        next if ( -d $src );
        if ( File::Compare::compare ($src, $target) )
        {
            Verbose ("Transfer $file");
            unlink $target;
            unless (File::Copy::copy( $src, $target ))
            {
                Error("Failed to transfer file [$file]: $!",
                      "Src: $src",
                      "Tgt: $target");
            }
        }
    }

    #
    #   Commit the workspace
    #   This will go back onto the trunk
    #
    $svn = NewSessionByWS( $opt_workdir );
    my $pkgPath = $svn->Path();

    my $ciComment = "$pkgPath: Checkin by Svn Import";
    $ciComment .= "\n" . $opt_log if ( $opt_log );
    $ciComment =~ s~\r\n~\n~g;
    $ciComment =~ s~\r~\n~g;
    $ciComment = encode('UTF-8', $ciComment, Encode::FB_DEFAULT);

    $svn->SvnCi ('comment' => $ciComment, 'allowSame' => 1 );
    Message ("Repository Ref: " . $svn->RmRef) unless( $opt_label );
    $svn->setRepoProperty('svn:author', $opt_author) if (defined ($opt_author));
    $svn->setRepoProperty('svn:date', $opt_date) if (defined ($opt_date));

    #
    #   Label the result
    #   The workspace will have been updated, so we can use it as the base for
    #   the labeling process
    #
    if ( $opt_label )
    {
        $svn->SvnCopyWs (
                       target => $url_label,
                       'noswitch' => 1,
                       'replace' => $opt_replace,
                       'comment' => "$pkgPath: Tagged by Jats Svn Import",
                       );
        Message ("Repository Ref: " . $svn->RmRef);
        $svn->setRepoProperty('svn:author', $opt_author) if (defined ($opt_author));
        $svn->setRepoProperty('svn:date', $opt_date) if (defined ($opt_date));
    }

    #
    #   Clean up
    #
    if ( $opt_delete && ! $opt_reuse )
    {
        Message ("Delete Workspace");
        rmtree( $opt_workdir );
    }

    #
    #   Automation data transfer
    #
    if ( defined $opt_datafile )
    {
        my $data = JatsProperties::New();

        $data->setProperty('Command'        , 'ImportPackage');
        $data->setProperty('Label'          , $opt_label);
        $data->setProperty('subversion.tag' , $svn->RmRef);

        $data->Dump('InfoFile') if ($opt_verbose);
        $data->store( $opt_datafile );
    }

    $opr_done = 1;
}

#-------------------------------------------------------------------------------
# Function        : DeleteBranch
#
# Description     : Delete the branch that a workspace is based upon
#
# Inputs          : 
#
# Returns         : 
#
sub DeleteBranch
{
    my $opt_path;
    my $opt_error = 0;
    #
    #   Parse more options
    #
    GetOptions (
                "help:+"        => \$opt_help,
                "manual:3"      => \$opt_help,
                "path:s"        => \$opt_path,
                ) || Error ("Invalid command line" );

    #
    #   Subcommand specific help
    #
    SubCommandHelp( $opt_help, "Delete Branch") if ($opt_help);

    #
    #   Sanity Tests
    #
    Message ("Delete Workspace Branch" );
    Error ("Too many arguments: @ARGV") if ( $#ARGV >= 0 );

    #
    #   Do all the hard work
    #
    $opt_path = '.' unless ( defined $opt_path );
    my $uref = NewSessionByWS($opt_path, 0, 1);
    my $ws_root = $uref->SvnLocateWsRoot(1);
    my $ws_url = $uref->FullWs();

    #
    #   Must be a branch
    #
    Error ("Workspace is not based on a branch")
        unless ( $ws_url =~ m ~/branches/~ );

    Message ("Deleting: " . $uref->{WSURL} );
    $uref->SvnDelete (
                      'target'    => $ws_url,
                      'comment'   => [$uref->Path().": Delete Branch",'Deleted by user command: jats svn delete-branch'],
                      );
    $opr_done = 1;
}


#-------------------------------------------------------------------------------
# Function        : SubCommandHelp
#
# Description     : Provide help on a subcommand
#
# Inputs          : $help_level             - Help Level 1,2,3
#                   $topic                  - Topic Name
#
# Returns         : This function does not return
#
sub SubCommandHelp
{
    my ($help_level, $topic) = @_;
    my @sections;
    #
    #   Spell out the section we want to display
    #
    #   Note:
    #   Due to bug in pod2usage can't use 'head1' by itself
    #   Each one needs a subsection.
    #
    push @sections, qw( NAME SYNOPSIS ) ;
    push @sections, qw( ARGUMENTS OPTIONS ) if ( $help_level > 1 );
    push @sections, qw( DESCRIPTION )       if ( $help_level > 2 );

    #
    #   Extract section from the POD
    #
    pod2usage({-verbose => 99,
               -noperldoc => 1,
               -sections => $topic . '/' . join('|', @sections) } );
}



#-------------------------------------------------------------------------------
#   Documentation
#   NOTE
#
#   Each subcommand MUST have
#   head1 section as used by the subcommand
#       This should be empty, as the contents will NOT be displayed
#   head2 sections called
#       NAME SYNOPSIS ARGUMENTS OPTIONS DESCRIPTION
#
#=head1 xxxxxx
#=head2 NAME
#=head2 SYNOPSIS
#=head2 ARGUMENTS
#=head2 OPTIONS
#=head2 DESCRIPTION
#

=pod

=for htmltoc    GENERAL::Subversion::

=head1 NAME

jats svn - Miscellaneous SubVersion Operations

=head1 SYNOPSIS

jats svn [options] command [command options]

 Options:
    -help[=n]              - Help message, [n=1,2,3]
    -man                   - Full documentation [-help=3]
    -verbose[=n]           - Verbose command operation

 Common Command Options:
    All command support suboptions to provide command specific help

    -help[=n]              - Help message, [n=1,2,3]
    -man                   - Full documentation [-help=3]

 Commands are:
    test                   - Test access to subversion
    paths                  - Display Subversion tag to URL conversions
    ls URL                 - List Repo contents for URL
    tag [URL]              - Convert URL or Path to a Release Manager Tag
    url [TAG]              - Convert TAG or Path to a Subversion URL
    delete-package URL     - Delete Package Subtree
    delete-branch          - Delete a Development Branch
    create URL             - Create a new package at URL
    import URL             - Import files to package at URL

 Use the command
    jats svn command -h
 for command specific help


=head1 OPTIONS

=over

=item B<-help[=n]>

Print a help message and exit. The level of help may be either 1, 2 or
3 for a full manual.

This option may be specified multiple times to increment the help level, or
the help level may be directly specified as a number.

=item B<-man>

This is the same as '-help=3'.
The complete help is produced in a man page format.

=item B<--verbose[=n]>

This option will increase the level of verbosity of the commands.

If an argument is provided, then it will be used to set the level, otherwise the
existing level will be incremented. This option may be specified multiple times.

=back

=head1 DESCRIPTION

This program provides a number of useful Subversion based operations.

=head1 Test Subversion

=head2 NAME

Test Subversion

=head2 SYNOPSIS

    jats svn test

=head2 DESCRIPTION

This command will ensure that the subversion command line utility can be
located. The command will report the version of the svn client found.

=head1 Subversion Paths

=head2 NAME

Subversion Paths

=head2 SYNOPSIS

    jats svn paths

=head2 DESCRIPTION

This command will display the base Tags and associated URLs that are used by
JATS to convert a 'Subversion Tag' into a full URLs that will be used to access
a physical repository.

The 'Tags' configuration is site-specific.

=head1 List Repository

=head2 NAME

List Repository

=head2 SYNOPSIS

    jats svn ls <URL>

=head2 DESCRIPTION

This command will take a URL and perform a 'svn' list operation. The URL will
be expanded to include the site specific repository.

=head1 Url to Tag

=head2 NAME

Url to Tag

=head2 SYNOPSIS

    jats svn tag [Option] [tag]

 Options:
    -help[=n]              - Help message, [n=1,2,3]
    -man                   - Full documentation [-help=3]
    -path=path             - Convert specified path
    -url=url               - Convert specified URL

=head2 DESCRIPTION

This command will convert a URL or a PATH to a Subversion Tag that can
be used within the remainder of the build system. If no PATH or URL is provided,
then the command uses a path of the current directory.

The command will convert either a PATH or a URL. It will not do both.

The command will use the configured Subversion URL prefixes to create the Tag.

If a PATH is to be converted, then the PATH must address a Subversion workspace.
The conversion will return a Tag to the root of the Workspace and Peg it to
the last committed version. The command will not determine if the workspace
contains modified files.

If a URL is to be converted, then the resultant value should be used with
caution. The result is only as good as the provided URL and may not address
the root of a package.

=head1 Tag to Url

=head2 NAME

Tag to Url

=head2 SYNOPSIS

    jats svn url [Option] [url]

 Options:
    -help[=n]              - Help message, [n=1,2,3]
    -man                   - Full documentation [-help=3]
    -path=path             - Convert specified path
    -url=url               - Convert specified URL

=head2 DESCRIPTION

This command will convert a TAG or a PATH to a full URL that can be used
directly by Subversion. If no PATH or TAG is provided, then the command uses a
path of the current directory.

The command will convert either a TAG or a URL. It will not do both.

The command will use the configured Subversion URL prefixes to expand the TAG.

If a PATH is to be converted, then the PATH must address a Subversion workspace.
The conversion will return a URL to the root of the Workspace and Peg it to
the last committed version. The command will not determine if the workspace
contains modified files.

If a TAG is to be converted, then the resultant value should be used with
caution. The result is only as good as the provided URL and may not address
the root of a package.

=head1 Delete a Package

=head2 NAME

Delete a Package

=head2 SYNOPSIS

jats svn delete-package URL [options]

 Options:
    -help[=n]              - Help message, [n=1,2,3]
    -man                   - Full documentation [-help=3]
    -verbose[=n]           - Verbose command operation

=head2 ARGUMENTS

The command takes one argument: The URL of the desired package.
This may be be:

=over

=item * A full URL

Complete with protocol and path information.

=item * A simple URL

JATS will prepend the site-specific repository location to the user provided URL

=back

=head2 OPTIONS

This command has no significant options, other than the general help options.

=head2 DESCRIPTION

This command will delete a package from the repository. It will ensure
that the package is a valid package, before it is deleted.

The command is intended to be used by test scripts, rather than users.

=head1 Delete Branch

=head2 NAME

Delete the Workspace Branch

=head2 SYNOPSIS

jats svn delete-branch [options]

 Options:
    -help[=n]              - Help message, [n=1,2,3]
    -man                   - Full documentation [-help=3]
    -verbose[=n]           - Verbose command operation
    -path=path             - Target workspace

=head2 ARGUMENTS

The command takes no arguments.

=head2 OPTIONS

=over

=item B<-path=path>

This options specifies the path of the target workspace. If not provided the
command will use the current directory.

=back

=head2 DESCRIPTION

This command will delete the branch associated with the workspace in the
specified path. It is intended to simplify the deletion of Private or
Development branches.

If the workspace is not linked to a 'branch' then the command will fail.

=head1 Create a Package Version

=head2 NAME

Create a Package Version

=head2 SYNOPSIS

jats svn [options] create URL [command options]

 Options:
    -help[=n]               - Help message, [n=1,2,3]
    -man                    - Full documentation [-help=3]
    -verbose[=n]            - Verbose command operation

 Command Options
    -help[=n]               - Provide command specific help
    -import=nnn             - Import directory tree
    -label=nnn              - Label it (trunk import only)
    -new                    - Package must not exist
    -replace                - Replace any existing versions
    -trunk                  - Import to trunk
    -tags=nnn               - Import to tags
    -branch=nnn             - Import to branches

=head2 ARGUMENTS

The command takes one argument: The URL of the desired package.
This may be be:

=over

=item * A full URL

Complete with protocol and path information.

=item * A simple URL

JATS will prepend the site-specific repository location to the user provided URL

=back

=head2 OPTIONS

=over

=item -help[=n]

Print a help message and exit. The level of help may be either 1, 2 or 3.

This option may be specified multiple times to increment the help level, or
the help level may be directly specified as a number.

=item -import=nnn

This option specifies the path of a subdirectory tree to import into the newly
created package. In not provided, then only a package skeleton will be created.

=item -label=nnn

This option specifies a label to place the imported source, if the source is
being imported to the 'trunk' of the package.

=item -new

This option specifies that the named package MUST not exist at all.

=item -replace

This option allows the program to replace any existing versions of the
imported source. It will allow the deletion of any existing trunk, tags or
branches.

=item -trunk

This option specifies that imported source will be placed on the trunk of the
package. This is the default mode of import.

The options -trunk, -tags and -branch are mutually exclusive.

=item -tags=nnn

This option specifies that imported source will be placed directly on the
named tag of the package.

The options -trunk, -tags and -branch are mutually exclusive.

=item -branch=nnn

This option specifies that imported source will be placed directly on the
named branch of the package.

The options -trunk, -tags and -branch are mutually exclusive.

=back

=head2 DESCRIPTION

This command will create a new package within a repository. It will ensure
that the package contains the three required subdirectories: trunk, tags and
branches.

The command will also ensure that packages are not placed at inappropriate
locations within the repository. It is not correct to place a package within
another package.

The command will, optionally, import a directory tree into the repository and,
optionally, label the package.

The package body may be imported to the 'trunk' or to a branch or a tag.
By default the data will be imported to the trunk and may be labeled (tagged).

Options allow the targets to be deleted if they exist or to ensure that they
are not present.

The command does not attempt to merge file versions within the repository. It
may result in multiple instances of a file within the repository. Use only for
simple imports. Use the 'import' command for more sophisticated import requirements.

=head1 Import directory to a Package

=head2 NAME

Import directory to a Package

=head2 SYNOPSIS

jats svn [options] import URL [command options]

 Options:
    -help[=n]               - Help message, [n=1,2,3]
    -man                    - Full documentation [-help=3]
    -verbose[=n]            - Verbose command operation

 Command Options
    -help[=n]               - Command specific help, [n=1,2,3]
    -verbose[=n]            - Verbose operation
    -package=name           - Name of source package
    -dir=path               - Path to new version
    -label=label            - Label the result
    -branch=branchName      - Base import on a branch
    -replace                - Allow the label to be replaced
    -reuse                  - Reuse the import directory
    -workspace=path         - Path and name of alternate workspace
    -[no]delete             - Deletes workspace after use. Default:yes
    -author=name            - Force author of changes
    -date=dateString        - Force date of changes
    -log=text               - Append text to the commit message
    -datafile=path          - Export tag data for automation


=head2 ARGUMENTS

The command takes one argument: The URL of the desired package.
This may be be:

=over

=item * A full URL

Complete with protocol and path information.

=item * A simple URL

JATS will prepend the site-specific repository location to the user provided URL

=back

=head2 OPTIONS

=over

=item -help[=n]

Print a help message and exit. The level of help may be either 1, 2 or 3.

This option may be specified multiple times to increment the help level, or
the help level may be directly specified as a number.

=item -verbose[=n]

This option will increase the level of verbosity of the utility.

If an argument is provided, then it will be used to set the level, otherwise the
existing level will be incremented. This option may be specified multiple times.


=item -package=name

Either this option or a bare URL on the command line must be provided. It
specifies the repository and package to be used as a basis for the work.

=item -dir=path

This option is mandatory. It specifies the path to a local directory that
contains a version of the software to be checked in.

=item -label=name

The resulting software version will be labeled with this tag, if it is provided.

A label name of TIMESTAMP will be treated in special manner. The name will be
replaced with a unique name based on the users name and the current date time.

=item -branch=branchName

This option will cause the importation to be referenced to the named branch.
If the branch does not exist it will be created. If it does exist then it will
be used.

If this option is not specified, then the importation will be based on the 'trunk'.

If the Workspace is provided, then it will be used independently of this option.

A branchName of TIMESTAMP will be treated in special manner. The name will be
replaced with a unique name based on the users name and the current date time.

=item -replace

This option, if provided, allows the label to be replaced.

=item -reuse

This option can be used to speed the creation of multiple versions in a scripted
environment. The option allows the utility to reuse the workspace if it exists.

=item -workspace=path

This option specifies an alternate workspace directory to create and use. The
default directory is "SvnImportDir" within the users current directory.

=item [no]delete

This option control the deletion of the workspace directory. By default the
directory will be deleted, unless re-use is also used.

=item -author=name

This option will force the author of changes as recorded in the repository.
The repository must be configured to allow such changes.

This option may not work for non-admin users.

=item -date=dateString

This option will force the date of the changes as recorded in the repository.
The repository must be configured to allow such changes.
The dateString is in a restricted ISO 8601 format: ie 2009-02-12T00:44:04.921324Z

This option may not work for non-admin users.

=item -log=text

This option will append the specified text to the commit message.
The first line of the commit message is fixed by the import tool.

=item -datafile=path

This option will cause the utility to create a data file to record the import
tag. It is used for automation of the import process.

=back

=head2 DESCRIPTION

Import a new version of a package to the trunk of the package. The utility
will only import changed files so that file history is preserved within the
repository.

This utility is used import software from another version control system
The utility will:

=over

=item *

Create a Work Space based on the current package version

The 'trunk' of the named package will be used as the base for the workspace,
unless modified with the -branch option.

=item *

Update files and directories

Determines the files and directories that have been added and deleted and
update the Workspace to reflect the new structure.

=item *

Check in the new version

=item *

Label the new version

=back

=cut