Rev 6653 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | RSS feed
######################################################################### COPYRIGHT - VIX IP PTY LTD ("VIX"). ALL RIGHTS RESERVED.## Module name : JatsSvnCore.pm# Module type : Jats Support Module# Compiler(s) : Perl# Environment(s): jats## Description : JATS LowLevel Subversion Interface Functions## Requires a subversion client to be present on the machine# Does require at least SubVersion 1.5# Uses features not available in 1.4## The package currently implements a set of functions# There are some intentional limitations:# 1) Non recursive# 2) Errors terminate operation## This package contains experimental argument passing# processes. Sometimes use a hash of arguments##......................................................................#require 5.008_002;use strict;use warnings;use JatsEnv;## Global Variables# Configuration variables imported from environment# Must be 'our' to work with EnvImport#our $GBE_SVN_PATH; # Optional: SVN bin directoryour $GBE_SVN_USERNAME; # Optional: User nameour $GBE_SVN_PASSWORD; # Optional: User passwrdour $USER;package JatsSvnCore;use JatsError;use JatsSystem;use IPC::Open3;use File::Path; # Instead of FileUtilsuse File::Basename;use Cwd;# automatically export what we need into namespace of caller.use Exporter();our (@ISA, @EXPORT, %EXPORT_TAGS, @EXPORT_OK);@ISA = qw(Exporter);@EXPORT = qw(SvnSessionSvnUserCmdSvnComment);@EXPORT_OK = qw(ProcessRevNo%SVN_URLS@SVN_URLS_LIST);%EXPORT_TAGS = (All => [@EXPORT, @EXPORT_OK]);## Package Global#my $svn; # Abs path to 'svn' utilitymy $stdmux; # Abs path to stdmux utlityour %SVN_URLS; # Exported repository URLsour @SVN_URLS_LIST; # Exported repository URLs scan order#-------------------------------------------------------------------------------# Function : BEGIN## Description : Module Initialization# Invoked by Perl as soon as possible# Setup environment variables# Calculate globals## Inputs : None## Returns : Nothing#sub BEGIN{## Determine authentication information# If not present then assume that the user is already athenticated#::EnvImportOptional('USER');::EnvImportOptional('GBE_SVN_USERNAME');::EnvImportOptional('GBE_SVN_PASSWORD');## User can provide a path to the svn utility# It will be used if its present#::EnvImportOptional('GBE_SVN_PATH', '');## For some reason thats not clear these EnvVars must be used in this function# for them to be available elsewhere.## No it doesn't make sence to me either# Problem seen on Linx. Not investigated on others#Debug ("GBE_SVN_USERNAME", $::GBE_SVN_USERNAME);Debug ("GBE_SVN_PASSWORD", $::GBE_SVN_PASSWORD);Debug ("GBE_SVN_PATH", $::GBE_SVN_PATH);$stdmux = LocateProgInPath ( 'stdmux');$svn = LocateProgInPath ( 'svn', '--All', '--Path=' . $::GBE_SVN_PATH );## Don't report errors in not finding svn and stdmux# Need to allow the help system to work.### Extract GBE_SVN_XXXX_URL information from the environment# XXXX is the first element of the repository path and will# be globally (VIX) unique# The value will be the URL to access this named repository path# It will normally include the repository path# The saved URL will be terminated with a single '/' to simplify usage#foreach ( sort keys %ENV ){if ( m ~^GBE_SVN_URL_*(.*)~ ){my $url = $ENV{$_};my $key = $1;$url =~ s~/*$~/~;$SVN_URLS{$key} = $url;## Ensure that it is in valid format# Four forms are supported, although not all should be used#if ( $url =~ m{^svn://[^/]+} ) {## Type is SVN server# Protocol + server name#} elsif ( $url =~ m{^https{0,1}://.+} ) {## Type is HTTP server# Protocol + server name + path on server#} elsif ( $url =~ m{^file:///+[A-Z]:/} ) {## Type is local Repo (file)# Windows absolute pathname# file:///I:/path/...#} elsif ( $url =~ m{^file:///+[^/]} ) {## Type is local Repo (file)# Unix absolute pathname# file:///path/...#} else {ReportError ("GBE_SVN_URL format not understood","$key: $url");}}}@SVN_URLS_LIST = reverse sort keys %SVN_URLS;ErrorDoExit();#DebugDumpData("%SVN_URLS", \%SVN_URLS, \@SVN_URLS_LIST);}#-------------------------------------------------------------------------------# Function : SvnSession## Description : Create a new SvnSession# Simply used to contain information about the operations## Inputs : Nothing## Returns : A blessed ref#sub SvnSession{my $self = {};## Delayed error reporting# Allows the the package to be used when SVN is not installed# as long as we don't want to use any of the features## Think of 'help' when svn is not yet installed#Error ("The JATS stdmux utility cannot be found" ) unless ( $stdmux );Error ("The svn utility cannot be found", "Configured Path: $::GBE_SVN_PATH") unless ( $svn );## Documented instance variables#$self->{REVNO} = undef; # Revision of last Repository operation$self->{ERROR_LIST} = []; # Last SVN operation errors$self->{RESULT_LIST} = []; # Last SVN operation results$self->{PRINTDATA} = 0; # Global control of ProcessRevNobless ($self, __PACKAGE__);}#-------------------------------------------------------------------------------# Function : SvnDelete## Description : Delete a directory within a repository# Intended to be used to remove tags and branches## Inputs : $self - Instance data# A hash of named arguments# target - Path to remove# comment - User comment# noerror - Don't panic on failure### Returns : True - delete failed and 'noerror' was present#sub SvnDelete{my $self = shift;my %opt = @_;Debug ("SvnDelete");Error ("Odd number of args to SvnDelete") unless ((@_ % 2) == 0);Error ("SvnDelete: No target specified" ) unless ( $opt{'target'} );my $error = $opt{'noerror'} ? '' : "SvnDelete: Target not deleted";my $rv = SvnCmd ($self, 'delete', $opt{'target'}, '-m', SvnComment( $opt{'comment'}, 'Deleted by SvnDelete' ),, { 'credentials' => 1,'error' => $error } );return $rv;}#-------------------------------------------------------------------------------# Function : SvnRename## Description : Rename something within a repository# Intended to be used to rename tags and branches## A few tricks# - Rename is the same as a copy-delete# but it doesn't work if the source is pegged# so we just use a copy.# - Need to ensure the target does not exist# because if it does then we may create a subdir# within it.## Inputs : $self - Instance data# A hash of named arguments# old - Location within the repository to copy from# new - Location within the repository to copy to# comment - Commit comment# revision - ref to returned revision tag# tag - ref to URL of the Check In# replace - True: Delete existing tag if present## Returns : Revision of the copy#sub SvnRename{my $self = shift;my %opt = @_;Debug ("SvnRename");Error ("Odd number of args to SvnRename") unless ((@_ % 2) == 0);## Insert defaults#my $old = $opt{old} || Error ("SvnRename: Source not specified" );my $new = $opt{new} || Error ("SvnRename: Target not specified" );## Validate the source# Must do this in case the target-delete fails#SvnValidateTarget ( $self,'cmd' => 'SvnRename','target' => $old,'require' => 1,);## Validate the target# Repo needs to be valid, but we may be able# to delete the target if it does exist#SvnValidateTarget ( $self,'target' => $new,'delete' => $opt{replace},);## The 'rename' command does not handle a pegged source# Detect this and use a 'copy' command# We don't need to delete the source - as its pegged.#my $cmd = ($old =~ m~@\d+$~) ? 'copy' : 'rename';SvnCmd ($self, $cmd, $old, $new, '-m', SvnComment($opt{'comment'},'Renamed by SvnRename'),, { 'credentials' => 1,'process' => \&ProcessRevNo, 'error' => "SvnRename: Target not renamed" } );CalcRmReference($self, $new );Message ("Tag is: " . $self->{RMREF} );return $self->{RMREF} ;}#-------------------------------------------------------------------------------# Function : SvnCopy## Description : Copy something within a repository# Intended to be used to copy tags and branches## A few tricks# - Need to ensure the target does not exist# because if it does then we may create a subdir# within it.## Inputs : $self - Instance data# A hash of named arguments# old - Location within the repository to copy from# new - Location within the repository to copy to# comment - Commit comment# revision - ref to returned revision tag# tag - ref to URL of the Check In# replace - True: Delete existing tag if present# cmd - Error Prefix# validated - Locations already validated# parents - Create parents as required## Returns : Revision of the copy#sub SvnCopy{my $self = shift;my %opt = @_;Debug ("SvnCopy");Error ("Odd number of args to SvnCopy") unless ((@_ % 2) == 0);## Insert defaults#my $cmd = $opt{'cmd'} || 'SvnCopy';my $old = $opt{old} || Error ("$cmd: Source not specified" );my $new = $opt{new} || Error ("$cmd: Target not specified" );## Validate the source# Must do this in case the target-delete fails#SvnValidateTarget ( $self,'cmd' => $cmd,'target' => $old,'require' => 1,);## Validate the target# Repo needs to be valid, but we may be able# to delete the target if it does exist#SvnValidateTarget ( $self,'cmd' => $cmd,'target' => $new,'delete' => $opt{replace},);## Copy the URLs#SvnCmd ($self , 'copy', $old, $new, '-m', SvnComment($opt{'comment'},"Copied by $cmd"), $opt{parents} ? '--parents' : '', { 'credentials' => 1, 'process' => \&ProcessRevNo, 'error' => "$cmd: Source not copied" } );CalcRmReference($self, $new );Verbose ("Tag is: " . $self->{RMREF} );return $self->{RMREF} ;}#-------------------------------------------------------------------------------# Function : SvnValidateTarget## Description : Validate a target within the repository# Optional allow the target to be deleted# Mostly used internally## Inputs : $self - Instance data# A hash of named arguments# target - Location within the repository to test# cmd - Name of command to use in messages# delete - Delete if it exists# require - Target must exist# available - Target must NOT exist# comment - Deletion comment# test - Just test existance# create - Create if it doesn't exist## Returns : May not return# 2 : Exists and was created# 1 : Exists# 0 : Not exist (any more)#sub SvnValidateTarget{my $self = shift;my %opt = @_;Debug ("SvnValidateTarget", $opt{target});Error ("Odd number of args to SvnValidateTarget") unless ((@_ % 2) == 0);## Validate options#Error ("SvnValidateTarget: No target specified") unless ( $opt{target} );$opt{cmd} = "SvnValidateTarget" unless ( $opt{cmd} );my $cmd = $opt{cmd};## Ensure that the target path does not exist# Cannot allow a 'copy'/'rename' to copy into an existing path as# Two problems:# 1) We end up copying the source into a subdir of# target path, which is not what we want.# 2) Should use update to do that sort of a job#unless ( SvnTestPath ( $self, $cmd, $opt{target} )){## Target does not exist#return 0 if ( $opt{'test'} || $opt{'available'} );## Create target if required#if ( $opt{create} ){$self->SvnCmd ('mkdir', $opt{target}, '-m', $self->Path() . ': Created by ' . $cmd, '--parents', { 'credentials' => 1,'error' => "SvnCreateBranch",'process' => \&ProcessRevNo} );return 2;}Error ("$cmd: Element does not exist", "Element: $opt{target}")if ( $opt{'require'} );}else{## Target DOES exist# - Good if the user requires the target# - Error unless the user is prepared to delete it#return 1if ( $opt{'require'} || $opt{'test'} || $opt{'create'} );Error ("$cmd: Element exists", "Element: $opt{target}")unless ( $opt{'delete'} );## The user has requested that an existing target be deleted#SvnCmd ($self, 'delete', $opt{target}, '-m', SvnComment($opt{'comment'},"Deleted by $cmd"),, { 'credentials' => 1,'error' => "$cmd: Element not deleted" } );}return 0;}#-------------------------------------------------------------------------------# Function : ProcessRevNo## Description : Callback function for SvnCmd to Extract a revision number# from the svn command output stream## Inputs : $self - Instance data# $line - Command output## Globals:## Returns : zero - we don't want to kill the command#sub ProcessRevNo{my ($self, $line ) = @_;if ( $line =~ m~Committed revision\s+(\d+)\.~i ){$self->{REVNO} = $1;} elsif ( $self->{PRINTDATA} ) {Message ( $line ) if $line;}return 0;}#-------------------------------------------------------------------------------# Function : SvnInfo## Description : Determine Subversion Info for a specified target## Inputs : $self - Instance Data# $url - Path or URL to get Info on# $tag - Name of tag within $self to store data# Currently InfoWs and InfoRepo## Returns : Non Zero if errors detected# Authentication errors are always reported#sub SvnInfo{my ($self, $url, $tag) = @_;Error ("Internal: SvnInfo. No Tag provided") unless ( defined $tag );Error ("Internal: SvnInfo. No URL provided") unless ( defined $url );## Only call once# Must simulate a good call#if ( exists $self->{$tag} ){#DebugDumpData("MeCache: $tag", $self );$self->{ERROR_LIST} = [];return 0;}## Get basic information on the target#$self->{'infoTag'} = $tag;$self->{$tag}{SvnInfoPath} = $url;my $rv = $self->SvnCmd ('info', $url, '--depth', 'empty', { 'credentials' => 1,'nosavedata' => 1,'process' => \&ProcessInfo});delete $self->{$tag} if ( @{$self->{ERROR_LIST}} );delete $self->{'infoTag'};#DebugDumpData("Me: $tag", $self );return $rv;}#-------------------------------------------------------------------------------# Function : ProcessInfo## Description : Process info for SvnInfo## Inputs : $self - Instance data# $line - Command output## Returns : zero - we don't want to kill the command#sub ProcessInfo{my ($self, $line ) = @_;Message ( $line ) if $self->{PRINTDATA};$line =~ m~(.*?):\s+(.*)~;$self->{$self->{'infoTag'}}{$1} = $2;return 0;}#-------------------------------------------------------------------------------# Function : SvnScanPath## Description : Internal helper function# Scan a directory and split contents into three groups## Inputs : $self - Instance data# $cmd - Command prefix for errros# $path - Path to test## Returns : $ref_files - Ref to array of files# $ref_dirs - Ref to array of dirs# $ref_svn - Ref to array of svn dirs# $found - True: Path found#sub SvnScanPath{my $self = shift;my ($cmd, $path) = @_;my @files;my @dirs;my @svn;Debug ("SvnScanPath");Verbose2 ("SvnScanPath: $path");## Read in the directory information# Just one level. Gets files and dirs#if ( ! SvnTestPath( $self, $cmd, $path, 1 ) ){## Path does not exist#return \@files, \@dirs, \@svn, 0;}## Path exists# Sort into three sets# - Svn Directories# - Other Directories# - Files#foreach ( @{$self->{RESULT_LIST}} ){if ( $_ eq 'trunk/' || $_ eq 'tags/' || $_ eq 'branches/' ) {push @svn, $_;} elsif ( substr ($_, -1) eq '/' ) {push @dirs, $_;} else {push @files, $_;}}return \@files, \@dirs, \@svn, 1;}#-------------------------------------------------------------------------------# Function : SvnTestPath## Description : Internal helper function# Test a path within the Repo for existance# Optionally read in immediate directory data## Inputs : $self - Instance data# $cmd - Command prefix for errros# $path - Path to test# $mode - True: Read in immediate data## Returns : True : Path found# False : Path is non-existent in revision## May populate @RESULT_LIST with 'immediate' data#sub SvnTestPath{my $self = shift;my ($cmd, $path, $mode) = @_;my $depth = $mode ? 'immediates' : 'empty';Debug ("SvnTestPath", @_);## Read in the directory information - but no data#if ( SvnCmd ( $self, 'list', $path, '--depth', $depth, {'credentials' => 1,})){## Error occurred# If the path does not exist then this is an error that# we can handle. The path does not exist in the Repository## Note: Different version of SVN / SVN server generate different# messages. Check many#foreach my $umsg ( @{$self->{ERROR_LIST}}){return 0if ( $umsg =~ m~' non-existent in that revision$~|| $umsg =~ m~' non-existent in revision ~|| $umsg =~ m~: No repository found in '~|| $umsg =~ m~: Error resolving case of '~|| $umsg =~ m~: W160013:~|| $umsg =~ m~: E200009:~);}Error ("$cmd: Unexpected error", @{$self->{ERROR_LIST}});}return 1;}#-------------------------------------------------------------------------------# Function : CalcRmReference## Description : Determine the Release Manager Reference for a SVN# operation## Inputs : $self - Instance data# $target - target# $self->{REVNO} - Revision number## Returns : RMREF - String Reference#sub CalcRmReference{my ($self, $target) = @_;Error ("CalcRmReference: No Target") unless ( $target );Debug ("CalcRmReference: $target");## Insert any revision information to create a pegged URL#my $peg = $self->{REVNO} || $self->{WSREVNO};$target .= '@' . $peg if $peg;## Attempt to Calculate Release Manager# SourcePath::Tag#if ( $self->{DEVBRANCH} ){my $sourcePath = $self->CalcSymbolicUrl($self->FullPath()) . '/' . $self->{DEVBRANCH};my $tag = 'Unknown';if ( $target =~ m~/tags/(.*)~ ) {$tag = $1;} else {$tag = $peg if ( $peg );}$self->{SVNTAG} = $sourcePath . '::' . $tag;}return $self->{RMREF} = $self->CalcSymbolicUrl($target);}#-------------------------------------------------------------------------------# Function : CalcSymbolicUrl## Description : Given a URL, return a symbolic URL## Inputs : $target - FULL URL## Returns : Imput string with a Symbolic URL if possible#sub CalcSymbolicUrl{my ($self, $target) = @_;## Take target and remove the reference to the local repository,# if its present. This will provide a ref that we can use on any site## Note: %SVN_URLS values will have a trailing '/'## Sort in reverse order to ensure that the default URL is found last# Do case-insensitive compare. Cut the system some slack.#foreach my $tag ( @SVN_URLS_LIST ){if ( $target =~ s~^\Q$SVN_URLS{$tag}\E~$tag/~i ){$target =~ s~^/~~;last;}}return $target;}#-------------------------------------------------------------------------------# Function : SvnComment## Description : Create a nice SVN comment from a string or an array## Inputs : user - User comment# default - Default comment## Comments may be:# 1) A string - Simple# 2) An array## Returns : A string comment#sub SvnComment{my ($user, $default) = @_;$user = $default unless ( $user );return '' unless ( $user );my $type = ref $user;if ( $type eq '' ) {return $user;} elsif ( $type eq 'ARRAY' ) {return join ("\n", @{$user});} else {Error ("Unknown comment type: $type");}}#-------------------------------------------------------------------------------# Function : SvnCredentials## Description : Return an array of login credentials# Used to extend command lines where repository access# is required.## There are security implications in using EnvVars# to contain passwords. Its best to avoid their use# and to let cached authentication from a user-session# handle the process.## Inputs : None## Returns : An array - may be empty#sub SvnCredentials{my @result;Verbose2 ("SvnCredentials: $::USER");if ( $::GBE_SVN_USERNAME ){Verbose2 ("SvnCredentials: GBE_SVN_USERNAME : $::GBE_SVN_USERNAME");Verbose2 ("SvnCredentials: GBE_SVN_PASSWORD : Defined" ) if ($::GBE_SVN_PASSWORD);push @result, '--no-auth-cache';push @result, '--username', $::GBE_SVN_USERNAME;push @result, '--password', $::GBE_SVN_PASSWORD if ($::GBE_SVN_PASSWORD);}return @result;}#-------------------------------------------------------------------------------# Function : SvnCmd## Description : Run a Subversion Command and capture/process the# output## See also SvnUserCmd## Inputs : $self - Instance data# Command arguments# Last argument may be a hash of options.# credentials - Add credentials# nosavedata - Don't save the data# process - Callback function# printdata - Print data# error - Error Message# Used as first line of an Error call## Returns : non-zero on errors detected# Authentication errors are detected and always reported#sub SvnCmd{my $self = shift;#Debug0 ("SvnCmd", @_);my $authenicationError;## Extract arguments and options# If last argument is a hesh, then its a hash of options#my $opt;$opt = pop @_if (@_ > 0 and UNIVERSAL::isa($_[-1],'HASH'));my $savedPrintData = $self->{PRINTDATA};$self->{PRINTDATA} = $opt->{'printdata'} if ( exists $opt->{'printdata'} );## All commands are non-interactive, prepend argument# Accept serve certs. Only applies to https connections. VisualSvn# perfers https and it uses self-signed certificates.#unshift @_, '--non-interactive', '--trust-server-cert';# Remove empty arguments.@_ = grep { $_ ne '' } @_;Verbose2 "SvnCmd $svn @_";## Prepend credentials, but don't show to users#unshift @_, SvnCredentials() if ( $opt->{'credentials'} );## Useful debugging## $self->{LAST_CMD} = [$svn, @_];## Reset command output data#$self->{ERROR_LIST} = [];$self->{RESULT_LIST} = [];# $self->{LAST_CMD} = \@_;## Make use of a wrapper program to mux the STDERR and STDOUT into# one stream (STDOUT). # This solves a lot of problems## Do not use IO redirection of STDERR because as this will cause a# shell (sh or cmd.exe) to be invoked and this makes it much# harder to kill on all platforms.## Use open3 as it allows the arguments to be passed# directly without escaping and without any shell in the way#local (*CHLD_OUT, *CHLD_IN);my $pid = open3( \*CHLD_IN, \*CHLD_OUT, '>&STDERR', $stdmux, $svn, @_);## Looks as though we always get a PID - even if the process dies# straight away or can't be found. I suspect that open3 doesn't set# $! anyway. I know it doesn't set $?#Debug ("Pid: $pid");Error ("Can't run command: $!") unless $pid;## Close the input handle# We don't have anything to send to this program#close(CHLD_IN);## Monitor the output from the utility# Have used stdmux to multiplex stdout and stderr## Note: IO::Select doesn't work on Windows :(# Note: Open3 will cause blocking unless both streams are read# Can read both streams becsue IO::Select doesn't work## Observation:# svn puts errors to STDERR# svn puts status to STDOUT#while (<CHLD_OUT>){s~\s+$~~;tr~\\/~/~;Verbose3 ( "SvnCmd:" . $_);m~^STD(...):(.+)~;my $data = $1 ? $2 : $_;next unless ( $data );if ( $1 && $1 eq 'ERR' ){## Process STDERR output#next if ($data =~ m~^QDBusConnection:~);push @{$self->{ERROR_LIST}}, $data;$authenicationError = 1 if ( $data =~ m~Could not authenticate~i );$authenicationError = 1 if ( $data =~ m~E215004: Authentication failed~i );$authenicationError = 1 if ( $data =~ m~E215004: No more credentials~i );}else{## Process STDOUT data#push @{$self->{RESULT_LIST}}, $data unless ($opt->{'nosavedata'});## If the user has specified a processing function then pass each# line to the specified function. A non-zero return will# be taken as a signal to kill the command.#if ( exists ($opt->{'process'}) && $opt->{'process'}($self, $data) ){kill 9, $pid;sleep(1);last;}}}close(CHLD_OUT);## MUST wait for the process# Under Windows if this is not done then we eventually fill up some# perl-internal structure and can't spawn anymore processes.#my $rv = waitpid ( $pid, 0);# Always process authentication errors# Even if user thinks they are handling errors## Spell out authentication errors# Appears that some users can't read manuals - let hope they can read screen#if ( $authenicationError ){$opt->{'error'} = 'Authentication Error';$self->{ERROR_LIST} = [];push @{$self->{ERROR_LIST}},'=' x 80,,'User must manually authenticate against the repository.','Use \'svn ls --depth empty ' . $self->Full() . '\'','Enter your Windows Credentials when prompted and save the password','=' x 80,;}## If an error condition was detected and the user has provided# an error message, then display the error## This simplifies the user error processing#if ( @{$self->{ERROR_LIST}} && $opt->{'error'} ){Error ( $opt->{'error'}, @{$self->{ERROR_LIST}} );}## Exit status has no meaning since open3 has been used# This is because perl does not treat the opened process as a child# Not too sure it makes any difference anyway##Debug ("Useless Exit Status: $rv");my $result = @{$self->{ERROR_LIST}} ? 1 : 0;Verbose3 ("Exit Code: $result");$self->{PRINTDATA} = $savedPrintData;return $result;}#-------------------------------------------------------------------------------# Function : SvnUserCmd## Description : Run a Subversion Command for interactive user# Intended to be used interactive# No data captured or processed# See also SvnCmd## Inputs : Command arguments# Last argument may be a hash of options.# credentials - Add credentials## Returns : Result code of the SVN command#sub SvnUserCmd{## Extract arguments and options# If last argument is a hash, then its a hash of options#my $opt;$opt = pop @_if (@_ > 0 and UNIVERSAL::isa($_[-1],'HASH'));Verbose2 "SvnUserCmd $svn @_";## Delayed error reporting# Allows the the package to be used when SVN is not installed# as long as we don't want to use any of the features## Think of 'help' when svn is not yet installed#Error ("The JATS stdmux utility cannot be found" ) unless ( $stdmux );Error ("The svn utility cannot be found", "Configured Path: $::GBE_SVN_PATH") unless ( $svn );## Prepend credentials, but don't show to users#unshift @_, SvnCredentials() if ( $opt->{'credentials'} );## Run the command#my $rv = system( $svn, @_ );Verbose2 "System Result Code: $rv";Verbose2 "System Result Code: $!" if ($rv);return $rv / 256;}#------------------------------------------------------------------------------1;