Subversion Repositories DevTools

Rev

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