Subversion Repositories DevTools

Rev

Rev 7236 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
267 dpurdie 1
########################################################################
6177 dpurdie 2
# COPYRIGHT - VIX IP PTY LTD ("VIX"). ALL RIGHTS RESERVED.
267 dpurdie 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
363 dpurdie 38
our $USER;
267 dpurdie 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
353 dpurdie 64
                    %SVN_URLS
65
                    @SVN_URLS_LIST
66
 
267 dpurdie 67
                );
68
 
69
%EXPORT_TAGS = (All => [@EXPORT, @EXPORT_OK]);
70
 
71
 
72
#
73
#   Package Global
74
#
353 dpurdie 75
my  $svn;                               # Abs path to 'svn' utility
76
my  $stdmux;                            # Abs path to stdmux utlity
77
our %SVN_URLS;                          # Exported repository URLs
78
our @SVN_URLS_LIST;                     # Exported repository URLs scan order
267 dpurdie 79
 
80
#-------------------------------------------------------------------------------
81
# Function        : BEGIN
82
#
83
# Description     : Module Initialization
84
#                   Invoked by Perl as soon as possible
85
#                       Setup environment variables
86
#                       Calculate globals
87
#
88
# Inputs          : None
89
#
90
# Returns         : Nothing
91
#
92
sub BEGIN
93
{
94
    #
95
    #   Determine authentication information
96
    #   If not present then assume that the user is already athenticated
97
    #
363 dpurdie 98
    ::EnvImportOptional('USER');
267 dpurdie 99
    ::EnvImportOptional('GBE_SVN_USERNAME');
100
    ::EnvImportOptional('GBE_SVN_PASSWORD');
101
 
102
    #
103
    #   User can provide a path to the svn utility
104
    #   It will be used if its present
105
    #
106
    ::EnvImportOptional('GBE_SVN_PATH', '');
107
 
297 dpurdie 108
    #
109
    #   For some reason thats not clear these EnvVars must be used in this function
110
    #   for them to be available elsewhere.
111
    #
112
    #   No it doesn't make sence to me either
113
    #   Problem seen on Linx. Not investigated on others
114
    #
115
    Debug ("GBE_SVN_USERNAME", $::GBE_SVN_USERNAME);
116
    Debug ("GBE_SVN_PASSWORD", $::GBE_SVN_PASSWORD);
117
    Debug ("GBE_SVN_PATH", $::GBE_SVN_PATH);
118
 
271 dpurdie 119
    $stdmux = LocateProgInPath ( 'stdmux');
311 dpurdie 120
    $svn    = LocateProgInPath ( 'svn', '--All', '--Path=' . $::GBE_SVN_PATH );
353 dpurdie 121
 
122
    #
365 dpurdie 123
    #   Don't report errors in not finding svn and stdmux
124
    #   Need to allow the help system to work.
125
    #
126
 
127
    #
353 dpurdie 128
    #   Extract GBE_SVN_XXXX_URL information from the environment
129
    #       XXXX is the first element of the repository path and will
5710 dpurdie 130
    #            be globally (VIX) unique
353 dpurdie 131
    #       The value will be the URL to access this named repository path
132
    #       It will normally include the repository path
133
    #       The saved URL will be terminated with a single '/' to simplify usage
134
    #
135
    foreach ( sort keys %ENV )
136
    {
137
        if ( m ~^GBE_SVN_URL_*(.*)~ )
138
        {
139
            my $url = $ENV{$_};
140
            my $key = $1;
141
            $url =~ s~/*$~/~;
142
            $SVN_URLS{$key} = $url;
143
 
144
            #
145
            #   Ensure that it is in valid format
146
            #   Four forms are supported, although not all should be used
147
            #
148
            if ( $url =~ m{^svn://[^/]+} ) {
149
                #
150
                #   Type is SVN server
151
                #   Protocol + server name
152
                #
153
            } elsif ( $url =~ m{^https{0,1}://.+} ) {
154
                #
155
                #   Type is HTTP server
156
                #   Protocol + server name + path on server
157
                #
158
            } elsif ( $url =~ m{^file:///+[A-Z]:/} ) {
159
                #
160
                #   Type is local Repo (file)
161
                #   Windows absolute pathname
162
                #   file:///I:/path/...
163
                #
164
            } elsif ( $url =~ m{^file:///+[^/]} ) {
165
                #
166
                #   Type is local Repo (file)
167
                #   Unix absolute pathname
168
                #   file:///path/...
169
                #
170
            } else {
171
                ReportError ("GBE_SVN_URL format not understood","$key: $url");
172
            }
173
 
174
        }
175
    }
176
    @SVN_URLS_LIST = reverse sort keys %SVN_URLS;
177
    ErrorDoExit();
178
#DebugDumpData("%SVN_URLS", \%SVN_URLS, \@SVN_URLS_LIST);
267 dpurdie 179
}
180
 
181
#-------------------------------------------------------------------------------
182
# Function        : SvnSession
183
#
184
# Description     : Create a new SvnSession
185
#                   Simply used to contain information about the operations
186
#
187
# Inputs          : Nothing
188
#
189
# Returns         : A blessed ref
190
#
191
sub SvnSession
192
{
193
    my $self = {};
194
 
195
    #
311 dpurdie 196
    #   Delayed error reporting
197
    #   Allows the the package to be used when SVN is not installed
198
    #   as long as we don't want to use any of the features
199
    #
200
    #   Think of 'help' when svn is not yet installed
201
    #
202
    Error ("The JATS stdmux utility cannot be found" ) unless ( $stdmux );
203
    Error ("The svn utility cannot be found", "Configured Path: $::GBE_SVN_PATH") unless ( $svn );
204
 
205
    #
267 dpurdie 206
    #   Documented instance variables
207
    #
208
    $self->{REVNO} = undef;         # Revision of last Repository operation
209
    $self->{ERROR_LIST} = [];       # Last SVN operation errors
210
    $self->{RESULT_LIST} = [];      # Last SVN operation results
211
    $self->{PRINTDATA} = 0;         # Global control of ProcessRevNo
212
 
213
    bless ($self, __PACKAGE__);
214
}
215
 
216
#-------------------------------------------------------------------------------
217
# Function        : SvnDelete
218
#
1403 dpurdie 219
# Description     : Delete a directory within a repository
267 dpurdie 220
#                   Intended to be used to remove tags and branches
221
#
222
# Inputs          : $self       - Instance data
223
#                   A hash of named arguments
224
#                       target     - Path to remove
225
#                       comment    - User comment
226
#                       noerror    - Don't panic on failure
227
#
228
#
229
# Returns         : True - delete failed and 'noerror' was present
230
#
231
sub SvnDelete
232
{
233
    my $self = shift;
234
    my %opt = @_;
235
    Debug ("SvnDelete");
236
    Error ("Odd number of args to SvnDelete") unless ((@_ % 2) == 0);
237
    Error ("SvnDelete: No target specified" ) unless ( $opt{'target'} );
238
 
239
    my $error =  $opt{'noerror'} ? '' : "SvnDelete: Target not deleted";
240
 
241
    my $rv = SvnCmd ($self, 'delete'
242
                    , $opt{'target'}
243
                    , '-m', SvnComment( $opt{'comment'}, 'Deleted by SvnDelete' ),
244
                    , { 'credentials' => 1,
245
                        'error' => $error } );
246
    return $rv;
247
}
248
 
249
 
250
#-------------------------------------------------------------------------------
251
# Function        : SvnRename
252
#
253
# Description     : Rename something within a repository
254
#                   Intended to be used to rename tags and branches
255
#
256
#                   A few tricks
257
#                   - Rename is the same as a copy-delete
258
#                     but it doesn't work if the source is pegged
259
#                     so we just use a copy.
260
#                   - Need to ensure the target does not exist
261
#                     because if it does then we may create a subdir
262
#                     within it.
263
#
264
# Inputs          : $self           - Instance data
265
#                   A hash of named arguments
266
#                       old      - Location within the repository to copy from
267
#                       new      - Location within the repository to copy to
268
#                       comment  - Commit comment
269
#                       revision - ref to returned revision tag
270
#                       tag      - ref to URL of the Check In
271
#                       replace  - True: Delete existing tag if present
272
#
273
# Returns         : Revision of the copy
274
#
275
sub SvnRename
276
{
277
    my $self = shift;
278
    my %opt = @_;
279
    Debug ("SvnRename");
280
    Error ("Odd number of args to SvnRename") unless ((@_ % 2) == 0);
281
 
282
    #
283
    #   Insert defaults
284
    #
285
    my $old = $opt{old} || Error ("SvnRename: Source not specified" );
286
    my $new = $opt{new} || Error ("SvnRename: Target not specified" );
287
 
288
    #
289
    #   Validate the source
290
    #   Must do this in case the target-delete fails
291
    #
292
    SvnValidateTarget ( $self,
293
                        'cmd'    => 'SvnRename',
294
                        'target' => $old,
295
                        'require' => 1,
296
                        );
297
 
298
    #
299
    #   Validate the target
300
    #   Repo needs to be valid, but we may be able
301
    #   to delete the target if it does exist
302
    #
303
    SvnValidateTarget ( $self,
304
                        'target' => $new,
305
                        'delete' => $opt{replace},
306
                        );
307
    #
308
    #   The 'rename' command does not handle a pegged source
309
    #   Detect this and use a 'copy' command
310
    #   We don't need to delete the source - as its pegged.
311
    #
312
    my $cmd = ($old =~ m~@\d+$~) ? 'copy' : 'rename';
313
    SvnCmd ($self, $cmd
314
                    , $old
315
                    , $new
316
                    , '-m', SvnComment($opt{'comment'},'Renamed by SvnRename'),
317
                    , { 'credentials' => 1,
318
                        'process' => \&ProcessRevNo
319
                      , 'error' => "SvnRename: Target not renamed" } );
320
 
321
 
322
    CalcRmReference($self, $new );
323
    Message ("Tag is: " . $self->{RMREF} );
324
    return $self->{RMREF} ;
325
}
326
 
327
#-------------------------------------------------------------------------------
328
# Function        : SvnCopy
329
#
330
# Description     : Copy something within a repository
331
#                   Intended to be used to copy tags and branches
332
#
333
#                   A few tricks
334
#                   - Need to ensure the target does not exist
335
#                     because if it does then we may create a subdir
336
#                     within it.
337
#
338
# Inputs          : $self           - Instance data
339
#                   A hash of named arguments
340
#                       old         - Location within the repository to copy from
341
#                       new         - Location within the repository to copy to
342
#                       comment     - Commit comment
343
#                       revision    - ref to returned revision tag
344
#                       tag         - ref to URL of the Check In
345
#                       replace     - True: Delete existing tag if present
346
#                       cmd         - Error Prefix
347
#                       validated   - Locations already validated
6653 dpurdie 348
#                       parents     - Create parents as required
267 dpurdie 349
#
350
# Returns         : Revision of the copy
351
#
352
sub SvnCopy
353
{
354
    my $self = shift;
355
    my %opt = @_;
356
    Debug ("SvnCopy");
357
    Error ("Odd number of args to SvnCopy") unless ((@_ % 2) == 0);
358
 
359
    #
360
    #   Insert defaults
361
    #
362
    my $cmd = $opt{'cmd'} || 'SvnCopy';
363
    my $old = $opt{old} || Error ("$cmd: Source not specified" );
364
    my $new = $opt{new} || Error ("$cmd: Target not specified" );
365
 
366
    #
367
    #   Validate the source
368
    #   Must do this in case the target-delete fails
369
    #
370
    SvnValidateTarget ( $self,
371
                        'cmd'    => $cmd,
372
                        'target' => $old,
373
                        'require' => 1,
374
                        );
375
 
376
    #
377
    #   Validate the target
378
    #   Repo needs to be valid, but we may be able
379
    #   to delete the target if it does exist
380
    #
381
    SvnValidateTarget ( $self,
382
                        'cmd'    => $cmd,
383
                        'target' => $new,
384
                        'delete' => $opt{replace},
385
                        );
386
    #
387
    #   Copy the URLs
388
    #
389
    SvnCmd ($self   , 'copy'
390
                    , $old
391
                    , $new
6653 dpurdie 392
                    , '-m', SvnComment($opt{'comment'},"Copied by $cmd")
393
                    , $opt{parents} ? '--parents' : ''
267 dpurdie 394
                    , { 'credentials' => 1
395
                      , 'process' => \&ProcessRevNo
396
                      , 'error' => "$cmd: Source not copied" } );
397
 
398
    CalcRmReference($self, $new );
2049 dpurdie 399
    Verbose ("Tag is: " . $self->{RMREF} );
267 dpurdie 400
    return $self->{RMREF} ;
401
}
402
 
403
 
404
#-------------------------------------------------------------------------------
405
# Function        : SvnValidateTarget
406
#
407
# Description     : Validate a target within the repository
408
#                   Optional allow the target to be deleted
409
#                   Mostly used internally
410
#
411
# Inputs          : $self           - Instance data
412
#                   A hash of named arguments
413
#                       target      - Location within the repository to test
414
#                       cmd         - Name of command to use in messages
415
#                       delete      - Delete if it exists
416
#                       require     - Target must exist
417
#                       available   - Target must NOT exist
418
#                       comment     - Deletion comment
419
#                       test        - Just test existance
379 dpurdie 420
#                       create      - Create if it doesn't exist
267 dpurdie 421
#
422
# Returns         : May not return
385 dpurdie 423
#                   2 : Exists and was created
424
#                   1 : Exists
425
#                   0 : Not exist (any more)
267 dpurdie 426
#
427
sub SvnValidateTarget
428
{
429
    my $self = shift;
430
    my %opt = @_;
431
    Debug ("SvnValidateTarget", $opt{target});
432
    Error ("Odd number of args to SvnValidateTarget") unless ((@_ % 2) == 0);
433
 
434
    #
435
    #   Validate options
436
    #
437
    Error ("SvnValidateTarget: No target specified") unless ( $opt{target} );
438
    $opt{cmd} = "SvnValidateTarget" unless ( $opt{cmd} );
439
    my $cmd = $opt{cmd};
440
 
441
    #
442
    #   Ensure that the target path does not exist
443
    #   Cannot allow a 'copy'/'rename' to copy into an existing path as
444
    #   Two problems:
445
    #       1) We end up copying the source into a subdir of
446
    #          target path, which is not what we want.
447
    #       2) Should use update to do that sort of a job
448
    #
449
    unless ( SvnTestPath ( $self, $cmd, $opt{target} ))
450
    {
451
        #
452
        #   Target does not exist
453
        #
454
        return 0 if ( $opt{'test'} || $opt{'available'} );
455
 
379 dpurdie 456
        #
457
        #   Create target if required
458
        #
459
        if ( $opt{create} )
460
        {
461
            $self->SvnCmd ('mkdir', $opt{target}
385 dpurdie 462
                           , '-m', $self->Path() . ': Created by ' . $cmd
379 dpurdie 463
                           , '--parents'
385 dpurdie 464
                           , { 'credentials' => 1
465
                              ,'error' => "SvnCreateBranch"
466
                              ,'process' => \&ProcessRevNo
467
                             } );
468
            return 2;
379 dpurdie 469
        }
470
 
267 dpurdie 471
        Error ("$cmd: Element does not exist", "Element: $opt{target}")
472
            if ( $opt{'require'} );
473
    }
474
    else
475
    {
476
        #
477
        #    Target DOES exist
478
        #       - Good if the user requires the target
479
        #       - Error unless the user is prepared to delete it
480
        #
481
        return 1
379 dpurdie 482
                if ( $opt{'require'} || $opt{'test'} || $opt{'create'}  );
267 dpurdie 483
 
484
        Error ("$cmd: Element exists", "Element: $opt{target}")
485
            unless ( $opt{'delete'} );
486
 
487
        #
488
        #   The user has requested that an existing target be deleted
489
        #
490
        SvnCmd ($self, 'delete'
491
                        , $opt{target}
492
                        , '-m', SvnComment($opt{'comment'},"Deleted by $cmd"),
493
                        , { 'credentials' => 1,
494
                            'error' => "$cmd: Element not deleted" } );
495
    }
496
    return 0;
497
}
498
 
499
#-------------------------------------------------------------------------------
500
# Function        : ProcessRevNo
501
#
502
# Description     : Callback function for SvnCmd to Extract a revision number
503
#                   from the svn command output stream
504
#
505
# Inputs          : $self           - Instance data
506
#                   $line           - Command output
507
#
508
#                   Globals:
509
#
510
# Returns         : zero - we don't want to kill the command
511
#
512
sub ProcessRevNo
513
{
514
    my ($self, $line ) = @_;
515
 
516
    if ( $line =~ m~Committed revision\s+(\d+)\.~i )
517
    {
518
        $self->{REVNO} = $1;
519
    } elsif ( $self->{PRINTDATA} ) {
520
        Message ( $line ) if $line;
521
    }
522
    return 0;
523
}
524
 
525
#-------------------------------------------------------------------------------
1329 dpurdie 526
# Function        : SvnInfo
527
#
528
# Description     : Determine Subversion Info for a specified target
529
#
530
# Inputs          : $self               - Instance Data
531
#                   $url                - Path or URL to get Info on
532
#                   $tag                - Name of tag within $self to store data
1403 dpurdie 533
#                                         Currently InfoWs and InfoRepo
1329 dpurdie 534
#
535
# Returns         : Non Zero if errors detected
2429 dpurdie 536
#                   Authentication errors are always reported
1329 dpurdie 537
#
538
sub SvnInfo
539
{
540
    my ($self, $url, $tag) = @_;
541
    Error ("Internal: SvnInfo. No Tag provided") unless ( defined $tag );
542
    Error ("Internal: SvnInfo. No URL provided") unless ( defined $url );
543
 
544
    #
545
    #   Only call  once
546
    #       Must simulate a good call
547
    #
548
    if ( exists $self->{$tag} )
549
    {
550
#DebugDumpData("MeCache: $tag", $self );
551
        $self->{ERROR_LIST} = [];
552
        return 0;
553
    }
554
 
555
    #
556
    #   Get basic information on the target
557
    #
558
    $self->{'infoTag'} = $tag;
559
    $self->{$tag}{SvnInfoPath} = $url;
560
    my $rv = $self->SvnCmd ('info', $url, '--depth', 'empty'
561
                    , { 'credentials' => 1,
562
                        'nosavedata' => 1,
563
                        'process' => \&ProcessInfo
564
                    }
565
     );
566
 
567
    delete $self->{$tag} if ( @{$self->{ERROR_LIST}} );
568
    delete $self->{'infoTag'};
569
#DebugDumpData("Me: $tag", $self );
570
    return $rv;
571
}
572
 
573
#-------------------------------------------------------------------------------
574
# Function        : ProcessInfo
575
#
576
# Description     : Process info for SvnInfo
577
#
578
# Inputs          : $self           - Instance data
579
#                   $line           - Command output
580
#
581
# Returns         : zero - we don't want to kill the command
582
#
583
sub ProcessInfo
584
{
585
    my ($self, $line ) = @_;
586
 
587
    Message ( $line ) if $self->{PRINTDATA};
588
    $line =~ m~(.*?):\s+(.*)~;
589
    $self->{$self->{'infoTag'}}{$1} = $2;
590
    return 0;
591
}
592
 
593
#-------------------------------------------------------------------------------
267 dpurdie 594
# Function        : SvnScanPath
595
#
596
# Description     : Internal helper function
597
#                   Scan a directory and split contents into three groups
598
#
599
# Inputs          : $self               - Instance data
600
#                   $cmd                - Command prefix for errros
601
#                   $path               - Path to test
602
#
603
# Returns         : $ref_files          - Ref to array of files
604
#                   $ref_dirs           - Ref to array of dirs
605
#                   $ref_svn            - Ref to array of svn dirs
606
#                   $found              - True: Path found
607
#
608
sub SvnScanPath
609
{
610
    my $self = shift;
611
    my ($cmd, $path) = @_;
612
    my @files;
613
    my @dirs;
614
    my @svn;
615
 
616
    Debug ("SvnScanPath");
617
    Verbose2 ("SvnScanPath: $path");
618
    #
619
    #   Read in the directory information
620
    #   Just one level. Gets files and dirs
621
    #
622
    if ( ! SvnTestPath( $self, $cmd, $path, 1 ) )
623
    {
624
        #
625
        #   Path does not exist
626
        #
627
        return \@files, \@dirs, \@svn, 0;
628
    }
629
 
630
    #
631
    #   Path exists
632
    #   Sort into three sets
633
    #       - Svn Directories
634
    #       - Other Directories
635
    #       - Files
636
    #
637
    foreach ( @{$self->{RESULT_LIST}} )
638
    {
639
        if ( $_ eq 'trunk/' || $_ eq 'tags/' || $_ eq 'branches/' ) {
640
            push @svn, $_;
641
 
642
        } elsif ( substr ($_, -1) eq '/' ) {
643
            push @dirs, $_;
644
 
645
        } else {
646
            push @files, $_;
647
        }
648
    }
649
 
650
    return \@files, \@dirs, \@svn, 1;
651
}
652
 
653
#-------------------------------------------------------------------------------
654
# Function        : SvnTestPath
655
#
656
# Description     : Internal helper function
657
#                   Test a path within the Repo for existance
658
#                   Optionally read in immediate directory data
659
#
660
# Inputs          : $self               - Instance data
661
#                   $cmd                - Command prefix for errros
662
#                   $path               - Path to test
663
#                   $mode               - True: Read in immediate data
664
#
665
# Returns         : True  : Path found
666
#                   False : Path is non-existent in revision
667
#
668
#                   May populate @RESULT_LIST with 'immediate' data
669
#
670
sub SvnTestPath
671
{
672
    my $self = shift;
673
    my ($cmd, $path, $mode) = @_;
674
    my $depth = $mode ? 'immediates' : 'empty';
675
    Debug ("SvnTestPath", @_);
676
 
677
    #
678
    #   Read in the directory information - but no data
679
    #
680
    if ( SvnCmd ( $self, 'list', $path
681
                        , '--depth', $depth
682
                        , {'credentials' => 1,}
683
                        ))
684
    {
685
        #
686
        #   Error occurred
687
        #   If the path does not exist then this is an error that
369 dpurdie 688
        #   we can handle. The path does not exist in the Repository
6619 dpurdie 689
        #   
690
        #   Note: Different version of SVN / SVN server generate different
691
        #         messages. Check many
267 dpurdie 692
        #
6619 dpurdie 693
        foreach my $umsg ( @{$self->{ERROR_LIST}})
694
        {
695
            return 0
696
                if (    $umsg =~ m~' non-existent in that revision$~
697
                     || $umsg =~ m~' non-existent in revision ~
698
                     || $umsg =~ m~: No repository found in '~
699
                     || $umsg =~ m~: Error resolving case of '~
700
                     || $umsg =~ m~: W160013:~
701
                     || $umsg =~ m~: E200009:~
702
                    );
703
        }
267 dpurdie 704
        Error ("$cmd: Unexpected error", @{$self->{ERROR_LIST}});
705
    }
706
    return 1;
707
}
708
 
709
#-------------------------------------------------------------------------------
710
# Function        : CalcRmReference
711
#
712
# Description     : Determine the Release Manager Reference for a SVN
713
#                   operation
714
#
715
# Inputs          : $self                   - Instance data
716
#                   $target                 - target
717
#                   $self->{REVNO}          - Revision number
718
#
719
# Returns         : RMREF - String Reference
720
#
721
sub CalcRmReference
722
{
723
    my ($self, $target) = @_;
724
    Error ("CalcRmReference: No Target") unless ( $target );
353 dpurdie 725
    Debug ("CalcRmReference: $target");
267 dpurdie 726
 
727
    #
353 dpurdie 728
    #   Insert any revision information to create a pegged URL
729
    #
369 dpurdie 730
    my $peg = $self->{REVNO} || $self->{WSREVNO};
731
    $target .= '@' . $peg if $peg;
353 dpurdie 732
 
733
    #
1403 dpurdie 734
    #   Attempt to Calculate Release Manager
735
    #       SourcePath::Tag
736
    #
737
    if ( $self->{DEVBRANCH} )
738
    {
739
        my $sourcePath = $self->CalcSymbolicUrl($self->FullPath()) . '/' . $self->{DEVBRANCH};
740
        my $tag = 'Unknown';
741
        if ( $target =~ m~/tags/(.*)~ ) {
742
            $tag = $1;
743
        } else {
744
            $tag = $peg if ( $peg );
745
        }
746
        $self->{SVNTAG} = $sourcePath . '::' . $tag;
747
    }
748
 
749
    return $self->{RMREF} = $self->CalcSymbolicUrl($target);
750
}
751
 
752
#-------------------------------------------------------------------------------
753
# Function        : CalcSymbolicUrl
754
#
755
# Description     : Given a URL, return a symbolic URL
756
#
757
# Inputs          : $target                 - FULL URL
758
#
759
# Returns         : Imput string with a Symbolic URL if possible
760
#
761
sub CalcSymbolicUrl
762
{
763
    my ($self, $target) = @_;
764
 
765
    #
267 dpurdie 766
    #   Take target and remove the reference to the local repository,
767
    #   if its present. This will provide a ref that we can use on any site
768
    #
353 dpurdie 769
    #   Note: %SVN_URLS values will have a trailing '/'
267 dpurdie 770
    #
353 dpurdie 771
    #   Sort in reverse order to ensure that the default URL is found last
399 dpurdie 772
    #   Do case-insensitive compare. Cut the system some slack.
353 dpurdie 773
    #
774
    foreach my $tag ( @SVN_URLS_LIST )
775
    {
399 dpurdie 776
        if ( $target =~ s~^\Q$SVN_URLS{$tag}\E~$tag/~i )
353 dpurdie 777
        {
778
            $target =~ s~^/~~;
779
            last;
780
        }
781
    }
1403 dpurdie 782
    return $target;
267 dpurdie 783
}
784
 
785
#-------------------------------------------------------------------------------
786
# Function        : SvnComment
787
#
788
# Description     : Create a nice SVN comment from a string or an array
789
#
790
# Inputs          : user            - User comment
791
#                   default         - Default comment
792
#
793
#                   Comments may be:
794
#                       1) A string - Simple
795
#                       2) An array
796
#
797
# Returns         : A string comment
798
#
799
sub SvnComment
800
{
801
    my ($user, $default) = @_;
802
 
803
    $user = $default unless ( $user );
804
    return '' unless ( $user );
805
 
806
    my $type = ref $user;
807
    if ( $type eq '' ) {
808
        return $user;
809
 
810
    } elsif ( $type eq 'ARRAY' ) {
811
        return join ("\n", @{$user});
812
 
813
    } else {
814
        Error ("Unknown comment type: $type");
815
    }
816
}
817
 
818
 
819
#-------------------------------------------------------------------------------
820
# Function        : SvnCredentials
821
#
822
# Description     : Return an array of login credentials
823
#                   Used to extend command lines where repository access
824
#                   is required.
825
#
826
#                   There are security implications in using EnvVars
827
#                   to contain passwords. Its best to avoid their use
828
#                   and to let cached authentication from a user-session
829
#                   handle the process.
830
#
831
# Inputs          : None
832
#
833
# Returns         : An array - may be empty
834
#
835
sub SvnCredentials
836
{
837
    my @result;
363 dpurdie 838
    Verbose2 ("SvnCredentials: $::USER");
267 dpurdie 839
    if ( $::GBE_SVN_USERNAME )
840
    {
363 dpurdie 841
        Verbose2 ("SvnCredentials: GBE_SVN_USERNAME : $::GBE_SVN_USERNAME");
842
        Verbose2 ("SvnCredentials: GBE_SVN_PASSWORD : Defined" ) if ($::GBE_SVN_PASSWORD);
843
 
267 dpurdie 844
        push @result, '--no-auth-cache';
845
        push @result, '--username', $::GBE_SVN_USERNAME;
846
        push @result, '--password', $::GBE_SVN_PASSWORD if ($::GBE_SVN_PASSWORD);
847
    }
848
 
849
    return @result;
850
}
851
 
852
#-------------------------------------------------------------------------------
853
# Function        : SvnCmd
854
#
855
# Description     : Run a Subversion Command and capture/process the
856
#                   output
857
#
858
#                   See also SvnUserCmd
859
#
860
# Inputs          : $self           - Instance data
861
#                   Command arguments
862
#                   Last argument may be a hash of options.
863
#                       credentials - Add credentials
864
#                       nosavedata  - Don't save the data
865
#                       process     - Callback function
1403 dpurdie 866
#                       printdata   - Print data
267 dpurdie 867
#                       error       - Error Message
868
#                                     Used as first line of an Error call
869
#
870
# Returns         : non-zero on errors detected
6653 dpurdie 871
#                   Authentication errors are detected and always reported
267 dpurdie 872
#
873
sub SvnCmd
874
{
875
    my $self = shift;
7326 dpurdie 876
    Debug ("SvnCmd");
2429 dpurdie 877
    my $authenicationError;
267 dpurdie 878
 
879
    #
880
    #   Extract arguments and options
881
    #   If last argument is a hesh, then its a hash of options
882
    #
883
    my $opt;
884
    $opt = pop @_
885
        if (@_ > 0 and UNIVERSAL::isa($_[-1],'HASH'));
886
 
1403 dpurdie 887
    my $savedPrintData = $self->{PRINTDATA};
888
    $self->{PRINTDATA} = $opt->{'printdata'} if ( exists $opt->{'printdata'} );
889
 
267 dpurdie 890
    #
891
    #   All commands are non-interactive, prepend argument
367 dpurdie 892
    #   Accept serve certs. Only applies to https connections. VisualSvn
893
    #   perfers https and it uses self-signed certificates.
267 dpurdie 894
    #
367 dpurdie 895
    unshift @_, '--non-interactive', '--trust-server-cert';
6653 dpurdie 896
 
897
    # Remove empty arguments.
898
    @_ = grep { $_ ne '' } @_;
267 dpurdie 899
    Verbose2 "SvnCmd $svn @_";
900
 
901
    #
902
    #   Prepend credentials, but don't show to users
903
    #
904
    unshift @_, SvnCredentials() if ( $opt->{'credentials'} );
905
 
906
    #
907
    # Useful debugging
908
    #
909
    # $self->{LAST_CMD} = [$svn, @_];
910
 
911
    #
912
    #   Reset command output data
913
    #
914
    $self->{ERROR_LIST} = [];
915
    $self->{RESULT_LIST} = [];
1403 dpurdie 916
#    $self->{LAST_CMD} = \@_;
267 dpurdie 917
 
918
    #
271 dpurdie 919
    #   Make use of a wrapper program to mux the STDERR and STDOUT into
920
    #   one stream (STDOUT). #   This solves a lot of problems
921
    #
267 dpurdie 922
    #   Do not use IO redirection of STDERR because as this will cause a
923
    #   shell (sh or cmd.exe) to be invoked and this makes it much
271 dpurdie 924
    #   harder to kill on all platforms.
267 dpurdie 925
    #
926
    #   Use open3 as it allows the arguments to be passed
927
    #   directly without escaping and without any shell in the way
928
    #
271 dpurdie 929
    local (*CHLD_OUT, *CHLD_IN);
930
    my $pid = open3( \*CHLD_IN, \*CHLD_OUT, '>&STDERR', $stdmux, $svn, @_);
267 dpurdie 931
 
932
    #
933
    #   Looks as though we always get a PID - even if the process dies
934
    #   straight away or can't be found. I suspect that open3 doesn't set
935
    #   $! anyway. I know it doesn't set $?
936
    #
937
    Debug ("Pid: $pid");
938
    Error ("Can't run command: $!") unless $pid;
939
 
940
    #
941
    #   Close the input handle
942
    #   We don't have anything to send to this program
943
    #
944
    close(CHLD_IN);
945
 
946
    #
947
    #   Monitor the output from the utility
271 dpurdie 948
    #   Have used stdmux to multiplex stdout and stderr
267 dpurdie 949
    #
950
    #   Note: IO::Select doesn't work on Windows :(
271 dpurdie 951
    #   Note: Open3 will cause blocking unless both streams are read
952
    #         Can read both streams becsue IO::Select doesn't work
267 dpurdie 953
    #
954
    #   Observation:
955
    #       svn puts errors to STDERR
956
    #       svn puts status to STDOUT
957
    #
958
    while (<CHLD_OUT>)
959
    {
960
        s~\s+$~~;
961
        tr~\\/~/~;
962
 
379 dpurdie 963
 
271 dpurdie 964
        Verbose3 ( "SvnCmd:" . $_);
965
        m~^STD(...):(.+)~;
966
        my $data = $1 ? $2 : $_;
967
        next unless ( $data );
968
 
969
        if ( $1 && $1 eq 'ERR' )
267 dpurdie 970
        {
271 dpurdie 971
            #
972
            #   Process STDERR output
973
            #
4119 dpurdie 974
            next if ($data =~ m~^QDBusConnection:~);
271 dpurdie 975
            push @{$self->{ERROR_LIST}}, $data;
2429 dpurdie 976
            $authenicationError = 1 if ( $data =~ m~Could not authenticate~i );
5206 dpurdie 977
            $authenicationError = 1 if ( $data =~ m~E215004: Authentication failed~i );
6353 dpurdie 978
            $authenicationError = 1 if ( $data =~ m~E215004: No more credentials~i );
267 dpurdie 979
        }
271 dpurdie 980
        else
981
        {
982
            #
983
            #   Process STDOUT data
984
            #
985
            push @{$self->{RESULT_LIST}}, $data unless ($opt->{'nosavedata'});
986
 
987
            #
988
            #   If the user has specified a processing function then pass each
989
            #   line to the specified function.  A non-zero return will
990
            #   be taken as a signal to kill the command.
991
            #
992
            if ( exists ($opt->{'process'}) && $opt->{'process'}($self, $data) )
993
            {
994
                kill 9, $pid;
2429 dpurdie 995
                sleep(1);
271 dpurdie 996
                last;
997
            }
998
        }
267 dpurdie 999
    }
1000
 
1001
    close(CHLD_OUT);
1002
 
1003
    #
1004
    #   MUST wait for the process
1005
    #   Under Windows if this is not done then we eventually fill up some
1006
    #   perl-internal structure and can't spawn anymore processes.
1007
    #
1008
    my $rv = waitpid ( $pid, 0);
1009
 
2429 dpurdie 1010
    #   Always process authentication errors
2764 dpurdie 1011
    #       Even if user thinks they are handling errors
267 dpurdie 1012
    #
2429 dpurdie 1013
    #   Spell out authentication errors
1014
    #   Appears that some users can't read manuals - let hope they can read screen
1015
    #
2764 dpurdie 1016
    if ( $authenicationError )
2429 dpurdie 1017
    {
1018
        $opt->{'error'} = 'Authentication Error';
1019
        $self->{ERROR_LIST} = [];
1020
        push @{$self->{ERROR_LIST}}
1021
            ,'=' x 80,
1022
            ,'User must manually authenticate against the repository.'
1023
            ,'Use \'svn ls --depth empty ' . $self->Full() . '\''
1024
            ,'Enter your Windows Credentials when prompted and save the password'
1025
            ,'=' x 80,
1026
            ;
1027
    }
1028
 
1029
    #
267 dpurdie 1030
    #   If an error condition was detected and the user has provided
1031
    #   an error message, then display the error
1032
    #
1033
    #   This simplifies the user error processing
1034
    #
1035
    if ( @{$self->{ERROR_LIST}} && $opt->{'error'}  )
1036
    {
1037
        Error ( $opt->{'error'}, @{$self->{ERROR_LIST}} );
1038
    }
1039
 
1040
    #
1041
    #   Exit status has no meaning since open3 has been used
1042
    #   This is because perl does not treat the opened process as a child
1043
    #   Not too sure it makes any difference anyway
1044
    #
1045
    #
1046
    Debug ("Useless Exit Status: $rv");
1047
    my $result = @{$self->{ERROR_LIST}} ? 1 : 0;
1048
    Verbose3 ("Exit Code: $result");
1049
 
1403 dpurdie 1050
    $self->{PRINTDATA} = $savedPrintData;
267 dpurdie 1051
    return $result;
1052
}
1053
 
271 dpurdie 1054
 
267 dpurdie 1055
#-------------------------------------------------------------------------------
1056
# Function        : SvnUserCmd
1057
#
1058
# Description     : Run a Subversion Command for interactive user
1059
#                   Intended to be used interactive
1060
#                   No data captured or processed
1061
#                   See also SvnCmd
1062
#
1063
# Inputs          : Command arguments
1064
#                   Last argument may be a hash of options.
1065
#                       credentials - Add credentials
1066
#
1067
# Returns         : Result code of the SVN command
1068
#
1069
sub SvnUserCmd
1070
{
1071
    #
1072
    #   Extract arguments and options
1403 dpurdie 1073
    #   If last argument is a hash, then its a hash of options
267 dpurdie 1074
    #
1075
    my $opt;
1076
    $opt = pop @_
1077
        if (@_ > 0 and UNIVERSAL::isa($_[-1],'HASH'));
1078
 
1079
    Verbose2 "SvnUserCmd $svn @_";
365 dpurdie 1080
 
267 dpurdie 1081
    #
365 dpurdie 1082
    #   Delayed error reporting
1083
    #   Allows the the package to be used when SVN is not installed
1084
    #   as long as we don't want to use any of the features
1085
    #
1086
    #   Think of 'help' when svn is not yet installed
1087
    #
1088
    Error ("The JATS stdmux utility cannot be found" ) unless ( $stdmux );
1089
    Error ("The svn utility cannot be found", "Configured Path: $::GBE_SVN_PATH") unless ( $svn );
1090
 
1091
    #
267 dpurdie 1092
    #   Prepend credentials, but don't show to users
1093
    #
1094
    unshift @_, SvnCredentials() if ( $opt->{'credentials'} );
1095
 
1096
 
1097
    #
1098
    #   Run the command
1099
    #
1100
    my $rv = system( $svn, @_ );
1101
    Verbose2 "System Result Code: $rv";
1102
    Verbose2 "System Result Code: $!" if ($rv);
1103
 
1104
     return $rv / 256;
1105
}
1106
 
1107
#------------------------------------------------------------------------------
1108
1;
363 dpurdie 1109