Subversion Repositories DevTools

Rev

Go to most recent revision | Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
267 dpurdie 1
########################################################################
2
# Copyright (C) 2008 ERG Limited, All rights reserved
3
#
4
# Module name   : JatsSvnCore.pm
5
# Module type   : Jats Support Module
6
# Compiler(s)   : Perl
7
# Environment(s): jats
8
#
9
# Description   : JATS LowLevel Subversion Interface Functions
10
#
11
#                 Requires a subversion client to be present on the machine
12
#                 Does require at least SubVersion 1.5
13
#                 Uses features not available in 1.4
14
#
15
#                 The package currently implements a set of functions
16
#                 There are some intentional limitations:
17
#                   1) Non recursive
18
#                   2) Errors terminate operation
19
#
20
#                 This package contains experimental argument passing
21
#                 processes. Sometimes use a hash of arguments
22
#
23
#......................................................................#
24
 
25
require 5.008_002;
26
use strict;
27
use warnings;
28
use JatsEnv;
29
 
30
#
31
#   Global Variables
32
#   Configuration variables imported from environment
33
#   Must be 'our' to work with EnvImport
34
#
35
our $GBE_SVN_PATH;                      # Optional: SVN bin directory
36
our $GBE_SVN_USERNAME;                  # Optional: User name
37
our $GBE_SVN_PASSWORD;                  # Optional: User passwrd
38
 
39
 
40
package JatsSvnCore;
41
 
42
use JatsError;
43
use JatsSystem;
44
use IPC::Open3;
45
 
46
 
47
use File::Path;             # Instead of FileUtils
48
use File::Basename;
49
use Cwd;
50
 
51
 
52
# automatically export what we need into namespace of caller.
53
use Exporter();
54
our (@ISA, @EXPORT, %EXPORT_TAGS, @EXPORT_OK);
55
@ISA         = qw(Exporter);
56
@EXPORT      = qw(
57
                    SvnSession
58
                    SvnUserCmd
59
                    SvnComment
60
 
61
                );
62
@EXPORT_OK =  qw(
63
                    ProcessRevNo
64
                );
65
 
66
%EXPORT_TAGS = (All => [@EXPORT, @EXPORT_OK]);
67
 
68
 
69
#
70
#   Package Global
71
#
72
my $svn;                                # Abs path to 'svn' utility
73
 
74
#-------------------------------------------------------------------------------
75
# Function        : BEGIN
76
#
77
# Description     : Module Initialization
78
#                   Invoked by Perl as soon as possible
79
#                       Setup environment variables
80
#                       Calculate globals
81
#
82
# Inputs          : None
83
#
84
# Returns         : Nothing
85
#
86
sub BEGIN
87
{
88
    #
89
    #   Determine authentication information
90
    #   If not present then assume that the user is already athenticated
91
    #
92
    ::EnvImportOptional('GBE_SVN_USERNAME');
93
    ::EnvImportOptional('GBE_SVN_PASSWORD');
94
 
95
    #
96
    #   User can provide a path to the svn utility
97
    #   It will be used if its present
98
    #
99
    ::EnvImportOptional('GBE_SVN_PATH', '');
100
 
101
    $svn = LocateProgInPath ( 'svn', '--All', '--Path=' . $::GBE_SVN_PATH );
102
    Error ("The svn utility cannot be found", "Configured Path: $GBE_SVN_PATH") unless ( $svn );
103
}
104
 
105
#-------------------------------------------------------------------------------
106
# Function        : SvnSession
107
#
108
# Description     : Create a new SvnSession
109
#                   Simply used to contain information about the operations
110
#
111
# Inputs          : Nothing
112
#
113
# Returns         : A blessed ref
114
#
115
sub SvnSession
116
{
117
    my $self = {};
118
 
119
    #
120
    #   Documented instance variables
121
    #
122
    $self->{REVNO} = undef;         # Revision of last Repository operation
123
    $self->{ERROR_LIST} = [];       # Last SVN operation errors
124
    $self->{RESULT_LIST} = [];      # Last SVN operation results
125
    $self->{PRINTDATA} = 0;         # Global control of ProcessRevNo
126
 
127
    bless ($self, __PACKAGE__);
128
}
129
 
130
#-------------------------------------------------------------------------------
131
# Function        : SvnDelete
132
#
133
# Description     : Delete a directory within a repostory
134
#                   Intended to be used to remove tags and branches
135
#
136
# Inputs          : $self       - Instance data
137
#                   A hash of named arguments
138
#                       target     - Path to remove
139
#                       comment    - User comment
140
#                       noerror    - Don't panic on failure
141
#
142
#
143
# Returns         : True - delete failed and 'noerror' was present
144
#
145
sub SvnDelete
146
{
147
    my $self = shift;
148
    my %opt = @_;
149
    Debug ("SvnDelete");
150
    Error ("Odd number of args to SvnDelete") unless ((@_ % 2) == 0);
151
    Error ("SvnDelete: No target specified" ) unless ( $opt{'target'} );
152
 
153
    my $error =  $opt{'noerror'} ? '' : "SvnDelete: Target not deleted";
154
 
155
    my $rv = SvnCmd ($self, 'delete'
156
                    , $opt{'target'}
157
                    , '-m', SvnComment( $opt{'comment'}, 'Deleted by SvnDelete' ),
158
                    , { 'credentials' => 1,
159
                        'error' => $error } );
160
    return $rv;
161
}
162
 
163
 
164
#-------------------------------------------------------------------------------
165
# Function        : SvnRename
166
#
167
# Description     : Rename something within a repository
168
#                   Intended to be used to rename tags and branches
169
#
170
#                   A few tricks
171
#                   - Rename is the same as a copy-delete
172
#                     but it doesn't work if the source is pegged
173
#                     so we just use a copy.
174
#                   - Need to ensure the target does not exist
175
#                     because if it does then we may create a subdir
176
#                     within it.
177
#
178
# Inputs          : $self           - Instance data
179
#                   A hash of named arguments
180
#                       old      - Location within the repository to copy from
181
#                       new      - Location within the repository to copy to
182
#                       comment  - Commit comment
183
#                       revision - ref to returned revision tag
184
#                       tag      - ref to URL of the Check In
185
#                       replace  - True: Delete existing tag if present
186
#
187
# Returns         : Revision of the copy
188
#
189
sub SvnRename
190
{
191
    my $self = shift;
192
    my %opt = @_;
193
    Debug ("SvnRename");
194
    Error ("Odd number of args to SvnRename") unless ((@_ % 2) == 0);
195
 
196
    #
197
    #   Insert defaults
198
    #
199
    my $old = $opt{old} || Error ("SvnRename: Source not specified" );
200
    my $new = $opt{new} || Error ("SvnRename: Target not specified" );
201
 
202
    #
203
    #   Validate the source
204
    #   Must do this in case the target-delete fails
205
    #
206
    SvnValidateTarget ( $self,
207
                        'cmd'    => 'SvnRename',
208
                        'target' => $old,
209
                        'require' => 1,
210
                        );
211
 
212
    #
213
    #   Validate the target
214
    #   Repo needs to be valid, but we may be able
215
    #   to delete the target if it does exist
216
    #
217
    SvnValidateTarget ( $self,
218
                        'target' => $new,
219
                        'delete' => $opt{replace},
220
                        );
221
    #
222
    #   The 'rename' command does not handle a pegged source
223
    #   Detect this and use a 'copy' command
224
    #   We don't need to delete the source - as its pegged.
225
    #
226
    my $cmd = ($old =~ m~@\d+$~) ? 'copy' : 'rename';
227
    SvnCmd ($self, $cmd
228
                    , $old
229
                    , $new
230
                    , '-m', SvnComment($opt{'comment'},'Renamed by SvnRename'),
231
                    , { 'credentials' => 1,
232
                        'process' => \&ProcessRevNo
233
                      , 'error' => "SvnRename: Target not renamed" } );
234
 
235
 
236
    CalcRmReference($self, $new );
237
    Message ("Tag is: " . $self->{RMREF} );
238
    return $self->{RMREF} ;
239
}
240
 
241
#-------------------------------------------------------------------------------
242
# Function        : SvnCopy
243
#
244
# Description     : Copy something within a repository
245
#                   Intended to be used to copy tags and branches
246
#
247
#                   A few tricks
248
#                   - Need to ensure the target does not exist
249
#                     because if it does then we may create a subdir
250
#                     within it.
251
#
252
# Inputs          : $self           - Instance data
253
#                   A hash of named arguments
254
#                       old         - Location within the repository to copy from
255
#                       new         - Location within the repository to copy to
256
#                       comment     - Commit comment
257
#                       revision    - ref to returned revision tag
258
#                       tag         - ref to URL of the Check In
259
#                       replace     - True: Delete existing tag if present
260
#                       cmd         - Error Prefix
261
#                       validated   - Locations already validated
262
#
263
# Returns         : Revision of the copy
264
#
265
sub SvnCopy
266
{
267
    my $self = shift;
268
    my %opt = @_;
269
    Debug ("SvnCopy");
270
    Error ("Odd number of args to SvnCopy") unless ((@_ % 2) == 0);
271
 
272
    #
273
    #   Insert defaults
274
    #
275
    my $cmd = $opt{'cmd'} || 'SvnCopy';
276
    my $old = $opt{old} || Error ("$cmd: Source not specified" );
277
    my $new = $opt{new} || Error ("$cmd: Target not specified" );
278
 
279
    #
280
    #   Validate the source
281
    #   Must do this in case the target-delete fails
282
    #
283
    SvnValidateTarget ( $self,
284
                        'cmd'    => $cmd,
285
                        'target' => $old,
286
                        'require' => 1,
287
                        );
288
 
289
    #
290
    #   Validate the target
291
    #   Repo needs to be valid, but we may be able
292
    #   to delete the target if it does exist
293
    #
294
    SvnValidateTarget ( $self,
295
                        'cmd'    => $cmd,
296
                        'target' => $new,
297
                        'delete' => $opt{replace},
298
                        );
299
    #
300
    #   Copy the URLs
301
    #
302
    SvnCmd ($self   , 'copy'
303
                    , $old
304
                    , $new
305
                    , '-m', SvnComment($opt{'comment'},"Copied by $cmd"),
306
                    , { 'credentials' => 1
307
                      , 'process' => \&ProcessRevNo
308
                      , 'error' => "$cmd: Source not copied" } );
309
 
310
    CalcRmReference($self, $new );
311
    Message ("Tag is: " . $self->{RMREF} );
312
    return $self->{RMREF} ;
313
}
314
 
315
 
316
#-------------------------------------------------------------------------------
317
# Function        : SvnValidateTarget
318
#
319
# Description     : Validate a target within the repository
320
#                   Optional allow the target to be deleted
321
#                   Mostly used internally
322
#
323
# Inputs          : $self           - Instance data
324
#                   A hash of named arguments
325
#                       target      - Location within the repository to test
326
#                       cmd         - Name of command to use in messages
327
#                       delete      - Delete if it exists
328
#                       require     - Target must exist
329
#                       available   - Target must NOT exist
330
#                       comment     - Deletion comment
331
#                       test        - Just test existance
332
#
333
# Returns         : May not return
334
#                   True : Exists
335
#                   False: Not exist (any more)
336
#
337
sub SvnValidateTarget
338
{
339
    my $self = shift;
340
    my %opt = @_;
341
    Debug ("SvnValidateTarget", $opt{target});
342
    Error ("Odd number of args to SvnValidateTarget") unless ((@_ % 2) == 0);
343
 
344
    #
345
    #   Validate options
346
    #
347
    Error ("SvnValidateTarget: No target specified") unless ( $opt{target} );
348
    $opt{cmd} = "SvnValidateTarget" unless ( $opt{cmd} );
349
    my $cmd = $opt{cmd};
350
 
351
    #
352
    #   Ensure that the target path does not exist
353
    #   Cannot allow a 'copy'/'rename' to copy into an existing path as
354
    #   Two problems:
355
    #       1) We end up copying the source into a subdir of
356
    #          target path, which is not what we want.
357
    #       2) Should use update to do that sort of a job
358
    #
359
    unless ( SvnTestPath ( $self, $cmd, $opt{target} ))
360
    {
361
        #
362
        #   Target does not exist
363
        #
364
        return 0 if ( $opt{'test'} || $opt{'available'} );
365
 
366
        Error ("$cmd: Element does not exist", "Element: $opt{target}")
367
            if ( $opt{'require'} );
368
    }
369
    else
370
    {
371
        #
372
        #    Target DOES exist
373
        #       - Good if the user requires the target
374
        #       - Error unless the user is prepared to delete it
375
        #
376
        return 1
377
                if ( $opt{'require'} || $opt{'test'} );
378
 
379
        Error ("$cmd: Element exists", "Element: $opt{target}")
380
            unless ( $opt{'delete'} );
381
 
382
        #
383
        #   The user has requested that an existing target be deleted
384
        #
385
        SvnCmd ($self, 'delete'
386
                        , $opt{target}
387
                        , '-m', SvnComment($opt{'comment'},"Deleted by $cmd"),
388
                        , { 'credentials' => 1,
389
                            'error' => "$cmd: Element not deleted" } );
390
    }
391
    return 0;
392
}
393
 
394
#-------------------------------------------------------------------------------
395
# Function        : ProcessRevNo
396
#
397
# Description     : Callback function for SvnCmd to Extract a revision number
398
#                   from the svn command output stream
399
#
400
# Inputs          : $self           - Instance data
401
#                   $line           - Command output
402
#
403
#                   Globals:
404
#
405
# Returns         : zero - we don't want to kill the command
406
#
407
sub ProcessRevNo
408
{
409
    my ($self, $line ) = @_;
410
 
411
    if ( $line =~ m~Committed revision\s+(\d+)\.~i )
412
    {
413
        $self->{REVNO} = $1;
414
    } elsif ( $self->{PRINTDATA} ) {
415
        Message ( $line ) if $line;
416
    }
417
    return 0;
418
}
419
 
420
#-------------------------------------------------------------------------------
421
# Function        : SvnScanPath
422
#
423
# Description     : Internal helper function
424
#                   Scan a directory and split contents into three groups
425
#
426
# Inputs          : $self               - Instance data
427
#                   $cmd                - Command prefix for errros
428
#                   $path               - Path to test
429
#
430
# Returns         : $ref_files          - Ref to array of files
431
#                   $ref_dirs           - Ref to array of dirs
432
#                   $ref_svn            - Ref to array of svn dirs
433
#                   $found              - True: Path found
434
#
435
sub SvnScanPath
436
{
437
    my $self = shift;
438
    my ($cmd, $path) = @_;
439
    my @files;
440
    my @dirs;
441
    my @svn;
442
 
443
    Debug ("SvnScanPath");
444
    Verbose2 ("SvnScanPath: $path");
445
    #
446
    #   Read in the directory information
447
    #   Just one level. Gets files and dirs
448
    #
449
    if ( ! SvnTestPath( $self, $cmd, $path, 1 ) )
450
    {
451
        #
452
        #   Path does not exist
453
        #
454
        return \@files, \@dirs, \@svn, 0;
455
    }
456
 
457
    #
458
    #   Path exists
459
    #   Sort into three sets
460
    #       - Svn Directories
461
    #       - Other Directories
462
    #       - Files
463
    #
464
    foreach ( @{$self->{RESULT_LIST}} )
465
    {
466
        if ( $_ eq 'trunk/' || $_ eq 'tags/' || $_ eq 'branches/' ) {
467
            push @svn, $_;
468
 
469
        } elsif ( substr ($_, -1) eq '/' ) {
470
            push @dirs, $_;
471
 
472
        } else {
473
            push @files, $_;
474
        }
475
    }
476
 
477
    return \@files, \@dirs, \@svn, 1;
478
}
479
 
480
#-------------------------------------------------------------------------------
481
# Function        : SvnTestPath
482
#
483
# Description     : Internal helper function
484
#                   Test a path within the Repo for existance
485
#                   Optionally read in immediate directory data
486
#
487
# Inputs          : $self               - Instance data
488
#                   $cmd                - Command prefix for errros
489
#                   $path               - Path to test
490
#                   $mode               - True: Read in immediate data
491
#
492
# Returns         : True  : Path found
493
#                   False : Path is non-existent in revision
494
#
495
#                   May populate @RESULT_LIST with 'immediate' data
496
#
497
sub SvnTestPath
498
{
499
    my $self = shift;
500
    my ($cmd, $path, $mode) = @_;
501
    my $depth = $mode ? 'immediates' : 'empty';
502
    Debug ("SvnTestPath", @_);
503
 
504
    #
505
    #   Read in the directory information - but no data
506
    #
507
    if ( SvnCmd ( $self, 'list', $path
508
                        , '--depth', $depth
509
                        , {'credentials' => 1,}
510
                        ))
511
    {
512
        #
513
        #   Error occurred
514
        #   If the path does not exist then this is an error that
515
        #   we can handle. The path does nto exist in the Repository
516
        #
517
        return 0
518
            if (    $self->{ERROR_LIST}[0] =~ m~' non-existent in that revision$~
519
                 || $self->{ERROR_LIST}[0] =~ m~: No repository found in '~
520
                 || $self->{ERROR_LIST}[0] =~ m~: Error resolving case of '~
521
                );
522
 
523
        Error ("$cmd: Unexpected error", @{$self->{ERROR_LIST}});
524
    }
525
    return 1;
526
}
527
 
528
#-------------------------------------------------------------------------------
529
# Function        : CalcRmReference
530
#
531
# Description     : Determine the Release Manager Reference for a SVN
532
#                   operation
533
#
534
# Inputs          : $self                   - Instance data
535
#                   $target                 - target
536
#                   $self->{REVNO}          - Revision number
537
#
538
# Returns         : RMREF - String Reference
539
#
540
sub CalcRmReference
541
{
542
    my ($self, $target) = @_;
543
    Debug ("CalcRmReference");
544
 
545
    Error ("CalcRmReference: No Target") unless ( $target );
546
 
547
    #
548
    #   Take target and remove the reference to the local repository,
549
    #   if its present. This will provide a ref that we can use on any site
550
    #
551
    #   Note: GBE_SVN_URL will have a trailing '/'
552
    #
553
    $target .= '@' . $self->{REVNO} if $self->{REVNO};
554
    $target =~ s~^\Q$::GBE_SVN_URL\E~~ if ( $::GBE_SVN_URL );
555
    return $self->{RMREF} = $target;
556
}
557
 
558
#-------------------------------------------------------------------------------
559
# Function        : SvnComment
560
#
561
# Description     : Create a nice SVN comment from a string or an array
562
#
563
# Inputs          : user            - User comment
564
#                   default         - Default comment
565
#
566
#                   Comments may be:
567
#                       1) A string - Simple
568
#                       2) An array
569
#
570
# Returns         : A string comment
571
#
572
sub SvnComment
573
{
574
    my ($user, $default) = @_;
575
 
576
    $user = $default unless ( $user );
577
    return '' unless ( $user );
578
 
579
    my $type = ref $user;
580
    if ( $type eq '' ) {
581
        return $user;
582
 
583
    } elsif ( $type eq 'ARRAY' ) {
584
        return join ("\n", @{$user});
585
 
586
    } else {
587
        Error ("Unknown comment type: $type");
588
    }
589
}
590
 
591
 
592
#-------------------------------------------------------------------------------
593
# Function        : SvnCredentials
594
#
595
# Description     : Return an array of login credentials
596
#                   Used to extend command lines where repository access
597
#                   is required.
598
#
599
#                   There are security implications in using EnvVars
600
#                   to contain passwords. Its best to avoid their use
601
#                   and to let cached authentication from a user-session
602
#                   handle the process.
603
#
604
# Inputs          : None
605
#
606
# Returns         : An array - may be empty
607
#
608
sub SvnCredentials
609
{
610
    my @result;
611
 
612
    if ( $::GBE_SVN_USERNAME )
613
    {
614
        push @result, '--no-auth-cache';
615
        push @result, '--username', $::GBE_SVN_USERNAME;
616
        push @result, '--password', $::GBE_SVN_PASSWORD if ($::GBE_SVN_PASSWORD);
617
    }
618
 
619
    return @result;
620
}
621
 
622
#-------------------------------------------------------------------------------
623
# Function        : SvnCmd
624
#
625
# Description     : Run a Subversion Command and capture/process the
626
#                   output
627
#
628
#                   See also SvnUserCmd
629
#
630
# Inputs          : $self           - Instance data
631
#                   Command arguments
632
#                   Last argument may be a hash of options.
633
#                       credentials - Add credentials
634
#                       nosavedata  - Don't save the data
635
#                       process     - Callback function
636
#                       error       - Error Message
637
#                                     Used as first line of an Error call
638
#
639
# Returns         : non-zero on errors detected
640
#
641
sub SvnCmd
642
{
643
    my $self = shift;
644
    Debug ("SvnCmd");
645
 
646
    #
647
    #   Extract arguments and options
648
    #   If last argument is a hesh, then its a hash of options
649
    #
650
    my $opt;
651
    $opt = pop @_
652
        if (@_ > 0 and UNIVERSAL::isa($_[-1],'HASH'));
653
 
654
    #
655
    #   All commands are non-interactive, prepend argument
656
    #
657
    unshift @_, '--non-interactive';
658
    Verbose2 "SvnCmd $svn @_";
659
 
660
    #
661
    #   Prepend credentials, but don't show to users
662
    #
663
    unshift @_, SvnCredentials() if ( $opt->{'credentials'} );
664
 
665
    #
666
    # Useful debugging
667
    #
668
    # $self->{LAST_CMD} = [$svn, @_];
669
 
670
    #
671
    #   Reset command output data
672
    #
673
    $self->{ERROR_LIST} = [];
674
    $self->{RESULT_LIST} = [];
675
 
676
    #
677
    #   Do not use IO redirection of STDERR because as this will cause a
678
    #   shell (sh or cmd.exe) to be invoked and this makes it much
679
    #   harder to kill on all platforms
680
    #
681
    #   Use open3 as it allows the arguments to be passed
682
    #   directly without escaping and without any shell in the way
683
    #
684
    local (*CHLD_OUT, *CHLD_IN, *CHLD_ERR);
685
    my $pid = open3( \*CHLD_IN, \*CHLD_OUT, \*CHLD_ERR, $svn, @_);
686
 
687
    #
688
    #   Looks as though we always get a PID - even if the process dies
689
    #   straight away or can't be found. I suspect that open3 doesn't set
690
    #   $! anyway. I know it doesn't set $?
691
    #
692
    Debug ("Pid: $pid");
693
    Error ("Can't run command: $!") unless $pid;
694
 
695
    #
696
    #   Close the input handle
697
    #   We don't have anything to send to this program
698
    #
699
    close(CHLD_IN);
700
 
701
    #
702
    #   Monitor the output from the utility
703
    #
704
    #   Note: IO::Select doesn't work on Windows :(
705
    #
706
    #   Monitor STDOUT until it gets closed
707
    #   Read STDERR once STDOUT runs out
708
    #
709
    #   Observation:
710
    #       svn puts errors to STDERR
711
    #       svn puts status to STDOUT
712
    #
713
    while (<CHLD_OUT>)
714
    {
715
        s~\s+$~~;
716
        tr~\\/~/~;
717
        next unless ( $_ );
718
        Verbose3 ( "SvnCmd resp:\"" . $_ . '"');
719
        push @{$self->{RESULT_LIST}}, $_ unless ($opt->{'nosavedata'});
720
 
721
        #
722
        #   If the user has specified a processing function then pass each
723
        #   line to the specified function.  A non-zero return will
724
        #   be taken as a signal to kill the command.
725
        #
726
        if ( exists ($opt->{'process'}) && $opt->{'process'}($self, $_) )
727
        {
728
            kill 9, $pid;
729
            last;
730
        }
731
    }
732
 
733
    #
734
    #   Any error messages from the program
735
    #
736
    while ( <CHLD_ERR> )
737
    {
738
        s~\s+$~~;
739
        tr~\\/~/~;
740
        Verbose3 ( "SvnCmd Eresp:" . $_);
741
        push @{$self->{ERROR_LIST}}, $_;
742
 
743
    }
744
    close(CHLD_OUT);
745
    close(CHLD_ERR);
746
 
747
    #
748
    #   MUST wait for the process
749
    #   Under Windows if this is not done then we eventually fill up some
750
    #   perl-internal structure and can't spawn anymore processes.
751
    #
752
    my $rv = waitpid ( $pid, 0);
753
 
754
    #
755
    #   If an error condition was detected and the user has provided
756
    #   an error message, then display the error
757
    #
758
    #   This simplifies the user error processing
759
    #
760
    if ( @{$self->{ERROR_LIST}} && $opt->{'error'}  )
761
    {
762
        Error ( $opt->{'error'}, @{$self->{ERROR_LIST}} );
763
    }
764
 
765
    #
766
    #   Exit status has no meaning since open3 has been used
767
    #   This is because perl does not treat the opened process as a child
768
    #   Not too sure it makes any difference anyway
769
    #
770
    #
771
    Debug ("Useless Exit Status: $rv");
772
    my $result = @{$self->{ERROR_LIST}} ? 1 : 0;
773
    Verbose3 ("Exit Code: $result");
774
 
775
    return $result;
776
}
777
 
778
#-------------------------------------------------------------------------------
779
# Function        : SvnUserCmd
780
#
781
# Description     : Run a Subversion Command for interactive user
782
#                   Intended to be used interactive
783
#                   No data captured or processed
784
#                   See also SvnCmd
785
#
786
# Inputs          : Command arguments
787
#                   Last argument may be a hash of options.
788
#                       credentials - Add credentials
789
#
790
# Returns         : Result code of the SVN command
791
#
792
sub SvnUserCmd
793
{
794
    #
795
    #   Extract arguments and options
796
    #   If last argument is a hesh, then its a hash of options
797
    #
798
    my $opt;
799
    $opt = pop @_
800
        if (@_ > 0 and UNIVERSAL::isa($_[-1],'HASH'));
801
 
802
    Verbose2 "SvnUserCmd $svn @_";
803
 
804
    #
805
    #   Prepend credentials, but don't show to users
806
    #
807
    unshift @_, SvnCredentials() if ( $opt->{'credentials'} );
808
 
809
 
810
    #
811
    #   Run the command
812
    #
813
    my $rv = system( $svn, @_ );
814
    Verbose2 "System Result Code: $rv";
815
    Verbose2 "System Result Code: $!" if ($rv);
816
 
817
     return $rv / 256;
818
}
819
 
820
#------------------------------------------------------------------------------
821
1;