Subversion Repositories DevTools

Rev

Rev 1328 | Rev 1343 | 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   : jats.sh
5
# Module type   : Makefile system
6
# Compiler(s)   : n/a
7
# Environment(s): jats
8
#
9
# Description   : JATS 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;
341 dpurdie 28
our $USER;
267 dpurdie 29
use JatsEnv;
30
 
31
package JatsSvn;
32
 
33
use JatsError;
34
use JatsSystem;
35
use JatsSvnCore qw(:All);
36
 
37
use File::Path;             # Instead of FileUtils
38
use File::Basename;
39
use Cwd;
40
 
41
 
42
# automatically export what we need into namespace of caller.
43
use Exporter();
44
our (@ISA, @EXPORT, %EXPORT_TAGS, @EXPORT_OK);
45
@ISA         = qw(Exporter JatsSvnCore);
46
 
47
@EXPORT      = qw(
48
                    NewSession
49
                    NewSessionByWS
50
                    NewSessionByUrl
51
 
52
                    SvnRmView
53
                    SvnIsaSimpleLabel
54
                    SvnComment
55
 
56
                    SvnUserCmd
361 dpurdie 57
 
58
                    SvnPath2Url
369 dpurdie 59
                    SvnPaths
267 dpurdie 60
                );
61
@EXPORT_OK =  qw(
62
                );
63
 
64
%EXPORT_TAGS = (All => [@EXPORT, @EXPORT_OK]);
65
 
66
#
67
#   Global Variables
68
#
69
 
70
#-------------------------------------------------------------------------------
71
# Function        : SvnCo
72
#
73
# Description     : Create a workspace
74
#                   Can be used to extract files, without creating the
75
#                   subversion control files.
76
#
77
# Inputs          : $self               - Instance data
78
#                   $RepoPath           - Within the repository
79
#                   $Path               - Local path
80
#                   Options             - Options
81
#                           --Export    - Export Only
1270 dpurdie 82
#                           --NoPrint   - Don't print files exported
267 dpurdie 83
#
84
# Returns         : Nothing
85
#
86
sub SvnCo
87
{
88
    my ($self, $RepoPath, $path, @opts) = @_;
89
    my $export = grep (/^--Export/, @opts );
1270 dpurdie 90
    $self->{PRINTDATA} = ! grep (/^--NoPrint/, @opts );
341 dpurdie 91
    Debug ("SvnCo", $RepoPath, $path);
267 dpurdie 92
 
93
    #
94
    #   Ensure that the output path does not exist
95
    #   Do not allow the user to create a local work space
96
    #   where one already exists
97
    #
98
    Error ("SvnCo: No PATH specified" ) unless ( $path );
99
    Error ("SvnCo: Target path already exists", "Path: " . $path ) if ( -e $path );
100
 
101
    #
102
    #   Build up the command line
103
    #
104
    my @args = $export ? 'export' : 'checkout';
105
    push @args, qw( --ignore-externals );
106
    push @args, $RepoPath, $path;
107
 
1270 dpurdie 108
 
267 dpurdie 109
    my @co_list;
110
    if ( $self->SvnCmd ( @args,
111
                            {
112
                                'process' => \&ProcessCo,
113
                                'data' => \@co_list,
114
                                'credentials' => 1,
115
                                'nosavedata' => 1,
116
                            }
117
                       ) || @co_list )
118
    {
119
        #
120
        #   We have a checkout limitation
121
        #   Delete the workspace and then report the error
122
        #
385 dpurdie 123
        #   Note: For some reason a simple rmtree doesn't work
124
        #         Nor does glob show all the directories
125
        #
267 dpurdie 126
        Verbose2 ("Remove WorkSpace: $path");
127
        rmtree( $path, IsVerbose(3) );
385 dpurdie 128
        rmtree( $path, IsVerbose(3) );
267 dpurdie 129
        Error ("Checking out Workspace", @{$self->{ERROR_LIST}}, @co_list );
130
    }
1270 dpurdie 131
    $self->{PRINTDATA} = 0;
267 dpurdie 132
    return;
133
 
134
    #
135
    #   Internal routine to scan each the checkout
136
    #
137
    #   Due to the structure of a SubVersion repository it would be
138
    #   possible for a user to extract the entire repository. This is
139
    #   not good as the repo could be very very large
140
    #
141
    #   Assume that the structure of the repo is such that our
142
    #   user is not allowed to extract a directory tree that contains
143
    #   key paths - such as /tags/ as this would indicate that they are
144
    #   attempting to extract something that is not a package
145
    #
146
    #
147
    sub ProcessCo
148
    {
149
        my $self = shift;
1270 dpurdie 150
        my $data = shift;
151
 
152
        if ( $self->{PRINTDATA} )
267 dpurdie 153
        {
1270 dpurdie 154
            #
155
            #   Pretty display for user
156
            #
157
            Information1 ("Extracting: $data");
158
        }
159
 
160
        if (  $data =~ m~((/)(tags|branches|trunk)(/|$))~ )
161
        {
267 dpurdie 162
            my $bad_dir = $1;
163
            push @{$self->{ERROR_LIST}}, "Checkout does not describe the root of a package. Contains: $bad_dir";
164
            return 1;
165
        }
166
 
167
        ##
168
        ##   Limit the size of the WorkSpace
169
        ##   This limit is a bit artificial, but does attempt to
170
        ##   limit views that encompass too much.
171
        ##
172
        #if ( $#{$self->{RESULT_LIST}} > 100 )
173
        #{
174
        #    Warning ("View is too large - DEBUG USE ONLY. WILL BE REMOVED" );
175
        #    push @{$self->{ERROR_LIST}}, "View too large";
176
        #    return 1;
177
        #}
178
    }
179
}
180
 
181
#-------------------------------------------------------------------------------
182
# Function        : SvnCi
183
#
184
# Description     : Check in the specified WorkSpace
185
#
186
# Inputs          : $self           - Instance data
187
#                   A hash of named arguments
188
#                       comment     - Commit comment
379 dpurdie 189
#                       allowSame   - Allow no change to the workspace
267 dpurdie 190
#
191
# Returns         : Tag of the checkin
192
#
193
sub SvnCi
194
{
195
    my $self = shift;
196
    my %opt = @_;
197
    my $status_url;
379 dpurdie 198
    my $ws_rev;
267 dpurdie 199
 
200
    Debug ("SvnCi");
201
    Error ("SvnCi: Odd number of args") unless ((@_ % 2) == 0);
202
 
203
    #
204
    #   Validate the source path
1328 dpurdie 205
    #   Note: populates %{$self->{InfoWs}} with 'info' data
267 dpurdie 206
    #
207
    my $path = SvnValidateWs ($self, 'SvnCi');
208
 
209
    #
1328 dpurdie 210
    #   Examine %{$self->{InfoWs}}, which has the results of an 'info'
267 dpurdie 211
    #   command the locate the URL.
212
    #
213
    #   This contains the target view space
214
    #   Sanity test. Don't allow Checkin to a /tags/ area
215
    #
1328 dpurdie 216
    $status_url = $self->{InfoWs}{URL};
217
    $ws_rev = $self->{InfoWs}{Revision};
379 dpurdie 218
 
267 dpurdie 219
    Error ("SvnCi: Cannot determine Repositoty URL")
220
        unless ( $status_url );
221
 
222
    Error ("SvnCi: Not allowed to commit to a 'tags' area", "URL: $status_url")
223
        if ( $status_url =~ m~/tags(/|$)~ );
224
 
225
    #
226
    #   Commit
1328 dpurdie 227
    #   Will modify Repo, so kill the cached Info
228
    #   Will only be a real issue if we tag in the same session
267 dpurdie 229
    #
1328 dpurdie 230
    delete $self->{'InfoWs'};
231
    delete $self->{'InfoRepo'};
232
 
267 dpurdie 233
    $self->SvnCmd ( 'commit', $path
234
                    , '-m', SvnComment( $opt{'comment'}, 'Created by SvnCi' ),
235
                    , { 'credentials' => 1,
236
                        'process' => \&ProcessRevNo,
379 dpurdie 237
                        'error' => "SvnCi: Copy Error",
238
                         }
267 dpurdie 239
                        );
379 dpurdie 240
 
241
    #
242
    #   No error and no commit
243
    #   Workspace was not changed, may be allowed
244
    #
245
    if ( ! $self->{REVNO} && $opt{allowSame} )
246
    {
247
        Warning ("SvnCi: Workspace matches Repository. No commit");
248
        $self->{REVNO} = $ws_rev;
249
    }
250
 
267 dpurdie 251
    Error ("SvnCi: Cannot determine Revision Number", @{$self->{RESULT_LIST}})
252
        unless ( $self->{REVNO} );
253
 
254
    #
255
    #   Update the view
256
    #   Doing this so that the local view contains an up to date
257
    #   revision number. If not done, and a 'copy' is done based
258
    #   on this view then the branch information will indicate that
259
    #   the copy is based on an incorrect version.
260
    #   This can be confusing!
261
    #
262
    $self->SvnCmd ( 'update'   , $path
263
                        , '--ignore-externals'
264
                        , { 'credentials' => 1,
265
                            'error' => "SvnCi: Updating WorkSpace" }
266
                        );
267
    #
268
    #   Pass the updated revision number back to the user
269
    #
270
    $self->CalcRmReference($status_url);
379 dpurdie 271
    Message ("Commit Tag is: " . $self->{RMREF} );
267 dpurdie 272
    return $self->{RMREF} ;
273
}
274
 
275
#-------------------------------------------------------------------------------
276
# Function        : SvnCreatePackage
277
#
278
# Description     : Create a package and any associated files
279
#
280
# Inputs          : $self        - Instance data
281
#                   A hash of named arguments
282
#                       package  - Name of the package
283
#                                  May include subdirs
284
#                       new      - True: Must not already exist
285
#                       replace  - True: Replace targets
286
#                       import   - DirTree to import
287
#                       label    - Tag for imported DirTree
288
#                       type     - Import TTB target
289
#
290
# Returns         : Revision of the copy
291
#
292
sub SvnCreatePackage
293
{
294
    my $self = shift;
295
    my %opt = @_;
296
    my $target;
297
 
298
    Debug ("SvnCreatePackage", @_);
299
    Error ("Odd number of args to SvnCreatePackage") unless ((@_ % 2) == 0);
300
    my %dirs = ( 'trunk/'       => 0,
301
                 'tags/'        => 0,
302
                 'branches/'    => 0 );
303
 
304
    #
305
    #   Sanity Tests
306
    #
307
    my $package = $self->Full || Error ("SvnCreatePackage: No package name provided");
308
    Error ("SvnCreatePackage: Invalid import path") if ( $opt{'import'} && ! -d $opt{'import'} );
309
    Error ("SvnCreatePackage: Tag without Import") if ( $opt{'label'} && ! $opt{'import'} );
310
    $opt{'label'} = SvnIsaSimpleLabel( $opt{'label'} ) if (  $opt{'label'} );
311
 
312
    #
313
    #   Package path cannot contain any of the keyword paths tags,trunk,branches
314
    #   as this would place a package with a package
315
    #
316
    Error ("Package path contains a reserved word ($1)", "Path: $package")
317
        if (  $package =~ m~/(tags|branches|trunk)(/|$)~ );
318
 
319
    #
320
    #   Package path cannot be pegged, or look like one
321
    #
322
    Error ("Package name contains a Peg ($1)", "Path: $package")
323
        if ( $package =~ m~.*(@\d+)$~ );
324
 
325
    #
326
    #   Determine TTB target
327
    #
328
    $opt{'type'} = 'trunk' unless ( $opt{'type'} );
329
    Error ("Invalid TTB type") unless ( $opt{'type'} =~ m{^(tags|branches|trunk)$} );
330
    Error ("Import without label") if ( $opt{'type'} ne  'trunk'  && ! $opt{'label'} );
331
 
332
    #
333
    #   Before we import data we must ensure that the targets do not exist
334
    #   Determine the import target(s)
335
    #
336
    my $import_target;
337
    my $copy_target;
338
 
339
    if ( $opt{'import'} )
340
    {
341
        #
342
        #   Primary target
343
        #   trunk, branck or tag
344
        #
345
        $import_target = $package . '/' . $opt{'type'};
346
        $import_target .= '/' .$opt{'label'} if ( $opt{'type'} ne 'trunk');
347
 
348
        $self->SvnValidateTarget( 'target'    => $import_target,
349
                                  'delete'    => $opt{'replace'},
350
                                  'available' => 1 );
351
 
352
        #
353
        #   Secondary target
354
        #   If primary is a trunk and a label is provided
355
        #
356
        if ( $opt{'type'} eq 'trunk' && $opt{'label'} )
357
        {
358
            $copy_target = $package . '/tags/' . $opt{'label'};
359
            $self->SvnValidateTarget( 'target'    => $copy_target,
360
                                      'delete'    => $opt{'replace'},
361
                                      'available' => 1 );
362
        }
363
    }
364
 
365
    #
366
    #   Probe to see if the package exists
367
    #
368
    my ( $ref_files, $ref_dirs, $ref_svn, $found ) = $self->SvnScanPath ( 'SvnCreatePackage', $package );
379 dpurdie 369
 
267 dpurdie 370
    if ( @$ref_dirs )
371
    {
372
        Error ("SvnCreatePackage: Package directory exists",
373
               "Cannot create a package here. Unexpected subdirectories:", @$ref_dirs);
374
    }
375
 
376
    if ( @$ref_files )
377
    {
378
        Warning ("SvnCreatePackage: Unexpected files found",
379
               "Path: $package",
380
               "Unexpected files found: @$ref_files");
381
    }
382
 
383
    if ( @$ref_svn )
384
    {
385
        #
386
        #   Do we need a new one
387
        #
388
        Error ("SvnCreatePackage: Package exists: $package") if $opt{'new'};
389
 
390
        #
391
        #   Some subversion files have been found here
392
        #   Create the rest
393
        #   Assume that the directory tree is good
394
        #
395
        #
396
        #   Some, or all, of the required package subdirectories exist
397
        #   Determine the new ones to created so that it can be done
398
        #   in an atomic step
399
        #
400
        delete $dirs{$_} foreach  ( @$ref_svn );
401
        if ( keys %dirs )
402
        {
403
            Warning ("SvnCreatePackage: Not all package subdirs present",
404
                     "Remaining dirs will be created",
405
                     "Found: @$ref_svn") if @$ref_svn;
406
        }
407
        else
408
        {
409
            Warning ("SvnCreatePackage: Package already present");
410
        }
411
    }
412
 
413
    #
414
    #   Create package directories that have not been discovered
415
    #       trunk
416
    #       branches
417
    #       tags
418
    #
419
    my @dirs;
420
    push @dirs, $package . '/' . $_ foreach ( keys %dirs );
421
    $target = $package . '/trunk';
422
 
423
    #
424
    #   Create missing directories - if any
425
    #
426
    if ( @dirs )
427
    {
428
        $self->SvnCmd ('mkdir', @dirs
379 dpurdie 429
                       , '-m', $self->Path() . ': Created by SvnCreatePackage'
267 dpurdie 430
                       , '--parents'
385 dpurdie 431
                       , { 'credentials' => 1
432
                           ,'error' => "SvnCreatePackage"
433
                           ,'process' => \&ProcessRevNo
434
                         } );
267 dpurdie 435
    }
436
 
437
    #
438
    #   Import data into the package if required
439
    #   Import data. Possible cases:
440
    #       - Import to trunk - and then tag it
441
    #       - Import to branches
442
    #       - Import to tags
443
    #
444
    if ( $import_target )
445
    {
446
        Verbose ("Importing directory into new package: $opt{'import'}");
447
 
448
        $target = $import_target;
449
        $self->{PRINTDATA} = 1;
450
        $self->SvnCmd ('import', $opt{'import'}
451
                        , $target
452
                        , '-m', 'Import by SvnCreatePackage'
453
                        , '--force'
454
                        , { 'credentials' => 1
455
                           ,'error' => "Import Incomplete"
456
                           ,'process' => \&ProcessRevNo
457
                          })
458
    }
459
 
460
    #
461
    #   If imported to the trunk AND a label is provided
462
    #   then tag the import as well.
463
    #   A simple URL copy
464
    #
465
    if ( $copy_target )
466
    {
467
        Verbose ("Labeling imported trunk: $opt{'label'} ");
468
        $target = $copy_target;
469
        $self->SvnCmd ('copy'  , $import_target
470
                        , $target
471
                        , '-m', 'Import tagged by SvnCreatePackage'
472
                        , { 'credentials' => 1
473
                          , 'process' => \&ProcessRevNo
474
                          , 'error' => "Import Incomplete" } );
475
    }
476
 
477
    #
478
    #   Pass the updated revision number back to the user
479
    #
480
    $self->CalcRmReference($target);
379 dpurdie 481
    Message ("Create Package Tag is: " . $self->{RMREF} );
267 dpurdie 482
    return $self->{RMREF} ;
483
}
484
 
485
#-------------------------------------------------------------------------------
486
# Function        : SvnRmView
487
#
488
# Description     : Remove a Subversion view
489
#                   Will run sanity checks and only remove the view if
490
#                   all is well
491
#
492
# Inputs          : A hash of named arguments
493
#                       path     - Path to local workspace
494
#                       modified - Array of files that are allowed to be modified
495
#                       force    - True: Force deletion
496
#
497
# Returns         :
498
#
499
sub SvnRmView
500
{
501
    my %opt = @_;
502
    Debug ("SvnRmView");
503
    Error ("Odd number of args to SvnRmView") unless ((@_ % 2) == 0);
504
 
505
    #
506
    #   Sanity test
507
    #
508
    my $path = $opt{'path'} || '';
509
    my $path_length = length ($path);
510
    Verbose2 ("Delete WorkSpace: $path");
511
 
512
    #
513
    #   If the path does not exist then assume that its already deleted
514
    #
515
    unless ( $path && -e $path )
516
    {
517
        Verbose2 ("SvnRmView: Path does not exist");
518
        return;
519
    }
520
 
521
    #
522
    #   Create information about the workspace
523
    #   This will also validate the path
524
    #
361 dpurdie 525
    my $session = NewSessionByWS ( $path, 0, 1 );
267 dpurdie 526
 
527
    #
528
    #   Validate the path
529
    #
530
    $session->SvnValidateWs ($path, 'SvnRmView');
531
 
532
    #
533
    #   Ask subversion if there are any files to be updated
534
    #   Prevent deletion of a view that has modified files
535
    #
536
    unless ( $opt{'force'} )
537
    {
538
        $session->SvnWsModified ( 'cmd' => 'SvnRmView', %opt );
539
    }
540
 
541
    #
542
    #   Now we can delete it
543
    #
544
    Verbose2 ("Remove WorkSpace: $path");
545
    rmtree( $path, IsVerbose(3) );
546
}
547
 
548
 
549
#-------------------------------------------------------------------------------
550
# Function        : SvnCopyWs
551
#
552
# Description     : Copy a workspace to a new place in the repository
553
#                   Effectively 'label' the workspace
554
#
555
#                   It would appear that the 'copy' command is very clever
556
#                   If a version-controlled file has been changed
557
#                   in the source workspace, then it will automatically be
558
#                   copied. This is a trap.
559
#
560
#                   Only allow a 'copy' if there are no modified
561
#                   files in the work space (unless overridden)
562
#
1328 dpurdie 563
#                   Only allow a 'copy' if the local workspace is
564
#                   up to date with respect with the repo. It possible
565
#                   to do a 'commit' and then a 'copy' (tag) and have
566
#                   unexpected results as the workspace has not been
567
#                   updated. This is a trap.
267 dpurdie 568
#
1328 dpurdie 569
#
267 dpurdie 570
# Inputs          : $self        - Instance data
571
#                   A hash of named arguments
572
#                       path     - Path to local workspace
573
#                       target   - Location within the repository to copy to
574
#                       comment  - Commit comment
575
#                       modified - Array of files that are allowed to
576
#                                  be modified in the workspace.
385 dpurdie 577
#                       noswitch        - True: Don't switch to the new URL
578
#                       replace         - True: Delete existing tag if present
579
#                       allowLocalMods  - True: Allow complex tagging
267 dpurdie 580
#
581
# Returns         : Revision of the copy
582
#
583
sub SvnCopyWs
584
{
585
    my $self = shift;
586
    my %opt = @_;
385 dpurdie 587
    my $rv;
267 dpurdie 588
    Debug ("SvnCopyWs");
589
    Error ("Odd number of args to SvnCopyWs") unless ((@_ % 2) == 0);
590
    Error ("SvnCopyWs: No Workspace" ) unless ( $self->{WS} );
591
 
592
    #
593
    #   Insert defaults
594
    #
595
    my $target = $opt{target} || Error ("SvnCopyWs: Target not specified" );
596
 
597
    #
598
    #   Validate the source path
599
    #
600
    my $path = SvnValidateWs ($self, 'SvnCopyWs');
601
 
602
    #
603
    #   Validate the target
604
    #   Cannot have a 'peg'
605
    #
606
    Error ("SvnCopyWs: Target contains a Peg: ($1)", $target)
607
        if ( $target =~ m~(@\d+)\s*$~ );
608
 
609
    #
1328 dpurdie 610
    #   Ensure the Workspace is up to date
611
    #       Determine the state of the Repo and the Workspace
612
    #
613
    $self->SvnInfo( $self->{WS} , 'InfoWs' );
614
    $self->SvnInfo( $self->FullWs, 'InfoRepo' );
615
 
616
    my $wsLastChangedRev = $self->{'InfoWs'}{'Last Changed Rev'} || Error ("SvnCopyWs: Bad info for Workspace");
617
    my $repoLastChangedRev = $self->{'InfoRepo'}{'Last Changed Rev'} || Error ("SvnCopyWs: Bad info for Repository");
618
 
619
    Verbose("WS Rev  : $wsLastChangedRev");
620
    Verbose("Repo Rev: $repoLastChangedRev");
621
 
622
    Error ('SvnCopyWs: The repository has been modified since the workspace was last updated.',
623
           'Possibly caused by a commit without an update.',
624
           'Update the workspace and try again.',
625
           "Last Changed Rev. Repo: $repoLastChangedRev. Ws:$wsLastChangedRev") if ( $repoLastChangedRev > $wsLastChangedRev );
626
 
627
    #
267 dpurdie 628
    #   Examine the workspace and ensure that there are no modified
629
    #   files - unless they are expected
630
    #
631
    $self->SvnWsModified ( 'cmd' => 'SvnCopyWs', %opt );
385 dpurdie 632
 
267 dpurdie 633
    #
634
    #   Validate the repository
635
    #   Ensure that the target does not exist
636
    #   The target may be deleted if it exists and allowed by the user
637
    #
638
    $self->SvnValidateTarget ( 'cmd'    => 'SvnCopyWs',
639
                        'target' => $target,
640
                        'delete' => $opt{replace},
641
                        'comment' => 'Deleted by SvnCopyWs'
642
                        );
369 dpurdie 643
 
267 dpurdie 644
    #
645
    #   Copy source to destination
1328 dpurdie 646
    #   Assuming the WorkSpace is up to date then, even though the source is a
647
    #   WorkSpace, the copy does not transfer data from the WorkSpace.
648
    #   It appears as though its all done on the server. This is good - and fast.
267 dpurdie 649
    #
1328 dpurdie 650
    #   If the Workspace is not up to date, then files that SVN thinks have not
651
    #   been transferred will be transferred - hence the need to update after
652
    #   a commit.
653
    #
385 dpurdie 654
    #   Moreover, files that are modified in the local workspace will
655
    #   be copied and checked into the target, but this is not nice.
267 dpurdie 656
    #
385 dpurdie 657
    $self->{PRINTDATA} = 1;
658
    $rv = $self->SvnCmd ( 'cp'  , $path
267 dpurdie 659
                        , $target
660
                        , '--parents'
661
                        , '-m', SvnComment( $opt{'comment'}, 'Created by SvnCopyWs' ),
662
                        , { 'process' => \&ProcessRevNo,
663
                            'credentials' => 1, }
385 dpurdie 664
                        );
665
    $self->{PRINTDATA} = 0;
666
    if ($rv)
267 dpurdie 667
    {
668
        #
669
        #   Error in copy
670
        #   Attempt to delete the target. Don't worry if we can't do that
671
        #
672
        my @err1 = @{$self->{ERROR_LIST}};
673
        $self->SvnCmd ( 'delete'
674
                    , $target
675
                    , '-m', 'Deleted by SvnCopyWs after creation failure'
676
                    , { 'credentials' => 1, }
677
               );
678
        Error ("SvnCopyWs: Copy Error", @err1);
679
    }
680
 
681
    Error ("SvnCopyWs: Cannot determine Revision Number", @{$self->{RESULT_LIST}})
682
        unless ( $self->{REVNO} );
683
 
684
    Verbose2 ("Copy committed as revision: " . $self->{REVNO} );
685
 
686
    unless ( $opt{'noswitch'} )
687
    {
688
        #
689
        #   Switch to the new URL
690
        #   This will link the Workspace with the copy that we have just made
691
        #
692
        $self->SvnCmd ( 'switch', $target
693
                         , $path
694
                         , { 'credentials' => 1,
695
                             'error' => "SvnCopyWs: Cannot switch to new URL" }
696
               );
697
    }
698
 
699
    #
700
    #   Pass the updated revision number back to the user
701
    #
702
    $self->CalcRmReference($target );
353 dpurdie 703
    #Message ("Tag is: " . $self->{RMREF} );
267 dpurdie 704
    return $self->{RMREF} ;
705
}
706
 
707
#-------------------------------------------------------------------------------
708
# Function        : SvnWsModified
709
#
710
# Description     : Test a Workspace for modified files
711
#                   Allow some files to be modified
712
#
713
# Inputs          : $self           - Instance data
714
#                   A hash of named arguments
715
#                       path        - Path to local workspace
716
#                       modified    - Files that are allowed to be modified
717
#                                     Relative to the 'path'
718
#                                     May be a single file or an array of files
385 dpurdie 719
#                       allowLocalMods - Only warn about local mods
267 dpurdie 720
#                       cmd         - Command name for error reporting
721
#
722
# Returns         :
723
#
724
sub SvnWsModified
725
{
726
    my $self = shift;
727
    my %opt = @_;
728
    Debug ("SvnWsModified");
729
    Error ("Odd number of args to SvnWsModified") unless ((@_ % 2) == 0);
730
 
731
    my $cmd = $opt{'cmd'} || 'SvnWsModified';
732
 
733
    #
734
    #   Validate the path
735
    #
736
    SvnValidateWs ($self, $cmd);
737
    my $path = $self->{WS};
738
    my $path_length = length ($path);
739
    Verbose2 ("Test Workspace for Modifications: $path");
740
 
741
    #
742
    #   Ask subversion if there are any files to be updated
743
    #
744
    $self->SvnCmd ('status', $path, {'error' => "Svn status command error"} );
745
 
746
    #
747
    #   Examine the list of modified files
748
    #
749
    if ( @{$self->{RESULT_LIST}} )
750
    {
751
        #
351 dpurdie 752
        #   Create a hash of files that are allowed to change
267 dpurdie 753
        #   These are files relative to the base of the view
754
        #
755
        #   The svn command has the 'path' prepended, so this
756
        #   will be removed as we process the commands
757
        #
758
        my %allowed;
759
        my @unexpected;
760
 
761
        if ( exists $opt{'modified'}  )
762
        {
763
            $allowed{'/' . $_} = 1 foreach ( ref ($opt{'modified'}) ? @{$opt{'modified'}} : $opt{'modified'}  );
764
        }
765
 
766
        #
767
        #   Process the list of modified files
768
        #   Do this even if we aren't allowed modified files as we
769
        #   still need to examine the status and kill off junk entries
770
        #   ie: ?, I, ! and ~
771
        #
772
        #    First column: Says if item was added, deleted, or otherwise changed
773
        #      ' ' no modifications
774
        #      'A' Added
775
        #      'C' Conflicted
776
        #      'D' Deleted
777
        #      'I' Ignored
778
        #      'M' Modified
779
        #      'R' Replaced
780
        #      'X' item is unversioned, but is used by an externals definition
781
        #      '?' item is not under version control
782
        #      '!' item is missing (removed by non-svn command) or incomplete
783
        #      '~' versioned item obstructed by some item of a different kind
784
        #
785
        foreach my $entry ( @{$self->{RESULT_LIST}} )
786
        {
787
            #
788
            #   Extract filename from line
351 dpurdie 789
            #       First 8 chars are status
267 dpurdie 790
            #       Remove WS path too
791
            #
1328 dpurdie 792
            if ( length $entry >= 8 + $path_length)
793
            {
794
                my $file = substr ( $entry, 8 + $path_length );
795
                next if ( $allowed{$file} );
796
            }
267 dpurdie 797
 
798
            #
799
            #   Examine the first char and rule out funny things
800
            #
801
            my $f1 =  substr ($entry, 0,1 );
802
            next if ( $f1 =~ m{[?I!~]} );
803
            push @unexpected, $entry;
804
        }
385 dpurdie 805
 
806
        if ( @unexpected )
807
        {
808
            if ( $opt{allowLocalMods} ) {
809
                Message ("Workspace contains locally modified files:", @unexpected);
810
            } else {
811
                Error ("Workspace contains unexpected modified files", @unexpected);
812
            }
813
        }
267 dpurdie 814
    }
815
}
816
 
817
#-------------------------------------------------------------------------------
818
# Function        : SvnListPackages
819
#
820
# Description     : Determine a list of packages within the repo
821
#                   This turns out to be a very slow process
822
#                   so don't use it unless you really really need to
823
#
1341 dpurdie 824
# Inputs          : $self       - Instance data
825
#                   $repo       - Name of the repository
826
#                   Last argument may be a hash of options.
827
#                           Progress    - True: Show progress
828
#                           Show        - >1 : display matched Tags and stats
829
#                                         >2 : display Packages
830
#                           Tag         - Enable Tag Matching
831
#                                         Value is the tag to match
267 dpurdie 832
#
1341 dpurdie 833
# Returns         : Ref to an array of all packages
834
#                   Ref to an array of all packahes with matched tag
267 dpurdie 835
#
836
sub SvnListPackages
837
{
1341 dpurdie 838
    #
839
    #   Extract arguments and options
840
    #   If last argument is a hesh, then its a hash of options
841
    #
842
    my $opt;
843
    $opt = pop @_
844
        if (@_ > 0 and UNIVERSAL::isa($_[-1],'HASH'));
267 dpurdie 845
 
1341 dpurdie 846
    my ($self, $repo) = @_;
847
 
848
    my @path_list = '';
267 dpurdie 849
    my @list;
1341 dpurdie 850
    my @mlist;
267 dpurdie 851
    my $scanned = 0;
852
    Debug ("SvnListPackages");
853
    while ( @path_list )
854
    {
855
        my $path = shift @path_list;
1341 dpurdie 856
        if ( $opt->{Progress} )
857
        {
858
            Message ("Reading: " . ( $path || 'RepoRoot') );
859
        }
267 dpurdie 860
        $scanned++;
1341 dpurdie 861
        my ( $ref_files, $ref_dirs, $ref_svn, $found ) = $self->SvnScanPath ( 'Listing Packages', join( '/', $repo, $path) );
267 dpurdie 862
 
863
        #
864
        #   If there are Subversion dirs (ttb) in this directory
865
        #   then this is a package. Add to list
866
        #
867
        push @list, $path if ( @$ref_svn );
868
 
869
        #
870
        #   Add subdirs to the list of paths to explore
871
        #
872
        foreach  ( @$ref_dirs )
873
        {
1341 dpurdie 874
            chop;                                   # Remove trailing '/'
875
            push @path_list, $path ? join('/', $path , $_) : $_; # Extend the path
267 dpurdie 876
        }
877
    }
878
 
1341 dpurdie 879
    if ( $opt->{Tag} )
880
    {
881
        my $tag = $opt->{Tag};
882
        foreach my $path ( sort @list )
883
        {
884
            Message ("Testing: $path") if ( $opt->{Progress} );
885
            if ( $self->SvnTestPath ( 'Listing Packages', join('/', $repo, $path, 'tags', $tag) ) )
886
            {
887
                push @mlist, $path;
888
            }
889
        }
890
    }
891
 
892
    if ( $opt->{Show} )
893
    {
894
        Message ("Found Tags:", @mlist );
895
        Message ("Found Packages:", @list ) if  $opt->{Show} > 2;
896
        Message ("Tags Found: " . scalar @mlist );
897
        Message ("Packages Found: " . scalar @list );
898
        Message ("Dirs Scanned: $scanned");
899
    }
900
 
901
    return \@list, \@mlist;
267 dpurdie 902
}
903
 
904
#-------------------------------------------------------------------------------
905
# Function        : ListLabels
906
#
907
# Description     : List labels within a given package
908
#
909
# Inputs          : $self               - Instance data
910
#                   $path               - path to label source
911
#
912
# Returns         : Ref to an array
913
#
914
sub ListLabels
915
{
916
    my ($self, $path) = @_;
917
    Debug ("ListLabels");
918
 
919
    my ( $ref_files, $ref_dirs, $ref_svn, $found ) = $self->SvnScanPath ( 'Listing Versions', $path );
920
 
921
    Error ("List: Path not found: $path") unless ( $found );
922
 
923
    #
924
    #   Dont report files - just directories
925
    #
926
    return $ref_dirs;
927
}
928
 
929
 
930
#-------------------------------------------------------------------------------
931
# Function        : SvnLocateWsRoot
932
#
933
# Description     : Given a WorkSpace, determine the root of the work space
934
#                   This is not as simple as you might think
935
#
936
#                   Algorithm
937
#                       svn ls ..
938
#                       Am I in the parent directory
939
#                       Repeat
940
#
369 dpurdie 941
#                   Updates 'WS' and 'WSURL'
942
#
267 dpurdie 943
# Inputs          : $self               - Instance data
944
#                   $test               - True: Don't die on error
945
#
946
# Returns         : Root of workspace as an absolute address
947
#                   Will not return if there is an error
948
#
949
sub SvnLocateWsRoot
950
{
951
    my ($self, $test) = @_;
952
    my @path;
953
    my $path = $self->{WS};
954
 
955
    Debug ("SvnLocateWsRoot");
956
    Error ("SvnLocateWsRoot: No Workspace") unless ( $path  );
957
    Verbose2 ("SvnLocateWsRoot: Start in $path");
958
 
959
    #
960
    #   Validate the source path
961
    #
962
    if ( SvnValidateWs ($self, 'SvnLocateWsRoot', $test) )
963
    {
964
        return undef;
965
    }
966
 
967
    #
968
    #   Need to sanitize the users path to ensure that the following
969
    #   algorithm works. Need:
970
    #       1) Absolute Path
971
    #       2) Not ending in '/'
972
    #
973
 
974
    #
975
    #   If we have a relative path then prepend the current directory
976
    #   An absolute path is:
977
    #           /aaa/aa/aa
978
    #       or  c:/aaa/aa/aa
979
    #
980
    $path = getcwd() . '/' . $path
981
        unless ( $path =~ m~^/|\w:/~  );
982
 
983
    #
984
    #   Walk the bits and remove ".." directories
985
    #       Done by pushing non-.. elements and poping last entry for .. elements.
986
    #   Have a leading "/" which is good.
987
    #
988
    #   Create a array of directories in the path
989
    #   Split on one or more \ or / separators
990
    #
991
    foreach ( split /[\\\/]+/ , $path )
992
    {
993
        next if ( $_ eq '.' );
994
        unless ( $_ eq '..' )
995
        {
996
            push @path, $_;
997
        }
998
        else
999
        {
1000
            Error ("SvnLocateWsRoot: Bad Pathname: $path")
1001
                if ( $#path <= 0 );
1002
            pop @path;
1003
        }
1004
    }
1005
 
369 dpurdie 1006
    #
1007
    #   Need to adjust the WSURL too
1008
    #   Break into parts and pop them off as we go
1009
    #   Add a dummy one to allow for the first iteration
1010
    #
1011
    my @wsurl = (split (/[\\\/]+/ , $self->{WSURL}), 'Dummy');
1012
 
267 dpurdie 1013
    Verbose2 ("Clean absolute path elements: @path");
1014
    PATH_LOOP:
1015
    while ( @path )
1016
    {
1017
        #
1018
        #   This directory element. Append / to assist in compare
1019
        #   Determine parent path
1020
        #
1021
        my $name = pop (@path) . '/';
1022
        my $parent = join ('/', @path );
369 dpurdie 1023
        pop @wsurl;
267 dpurdie 1024
 
1025
        #
1026
        #   Examine the parent directory
1027
        #   Get a list of all elements in the parent
1028
        #   Need to ensure that this directory is one of them
1029
        #
1030
        #   Ignore any errors - assume that they are because the
1031
        #   parent is not a part of the work space. This will terminate the
1032
        #   search.
1033
        #
1034
        $self->SvnCmd ('list', $parent, '--depth', 'immediates' );
1035
        foreach my $entry ( @{$self->{RESULT_LIST}} )
1036
        {
1037
            next PATH_LOOP
1038
                if ( $entry eq $name );
1039
        }
1040
 
1041
        #
1042
        #   Didn't find 'dir' in directory svn listing of parent
1043
        #   This parent is not a part of the same WorkSpace as 'dir'
1044
        #   We have a winner.
1045
        #
1046
        chop $name;                         #   Chop the '/' previously added
1047
        $self->{WS} = $parent . '/' . $name;
369 dpurdie 1048
 
1049
        #
1050
        #   Reform the WSURL. Elements have been removed as we tested up the
1051
        #   path
1052
        #
1053
        $self->{WSURL} = join '/', @wsurl;
1054
 
267 dpurdie 1055
        return $self->{WS};
1056
    }
1057
 
1058
    #
1059
    #   Shouldn't get this far
1060
    #
1061
    Error ("SvnLocateWsRoot: Root not found");
1062
}
1063
 
1064
#-------------------------------------------------------------------------------
1065
# Function        : SvnValidateWs
1066
#
1067
# Description     : Validate the path to a working store
1068
#
1069
# Inputs          : $self           - Instance data
1070
#                   $user           - Optional prefix for error messages
1071
#                   $test           - True: Just test, Else Error
1072
#
1073
# Returns         : Will not return if not a workspace
1074
#                   Returns the users path
1328 dpurdie 1075
#                   Populates the hash: $self->{InfoWs}
267 dpurdie 1076
#
1077
sub SvnValidateWs
1078
{
1079
    my ($self, $user, $test) = @_;
1080
    Debug ("SvnValidateWs");
1081
 
1082
    $user = "Invalid Subversion Workspace" unless ( $user );
1328 dpurdie 1083
    my $path = $self->{WS};
267 dpurdie 1084
 
1085
    #
1328 dpurdie 1086
    #   Only validate it once
267 dpurdie 1087
    #
1088
    return $path if ( $self->{WS_VALIDATED} );
1089
 
1090
    #
1091
    #   Validate the source path
1092
    #   Must exist and must be a directory
1093
    #
1094
    if ( ! $path ) {
1095
        @{$self->{ERROR_LIST}} = "$user: No path specified";
1096
 
1097
    } elsif ( ! -e $path ) {
1098
        @{$self->{ERROR_LIST}} = "$user: Path does not exist: $path";
1099
 
1100
    } elsif ( ! -d $path ) {
1101
        @{$self->{ERROR_LIST}} = "$user: Path is not a directory";
1102
    } else {
1103
        #
1104
        #   Determine the source path is an fact a view
1105
        #   The info command can do this. Use depth empty to limit the work done
1106
        #
1328 dpurdie 1107
        $self->SvnInfo($path, 'InfoWs');
267 dpurdie 1108
 
1109
        #
1110
        #   Error. Prepend nice message
1111
        #
1112
        unshift @{$self->{ERROR_LIST}}, "$user: Path is not a WorkSpace: $path"
1113
            if ( @{$self->{ERROR_LIST}} );
1114
    }
1115
 
1116
    #
1117
    #   Figure out what to do
1118
    #
1119
    if ( $test )
1120
    {
1121
        return @{$self->{ERROR_LIST}};
1122
    }
1123
    else
1124
    {
1125
        Error @{$self->{ERROR_LIST}} if @{$self->{ERROR_LIST}};
1126
        $self->{WS_VALIDATED} = 1;
1127
        return $path;
1128
    }
1129
}
1130
 
1131
#-------------------------------------------------------------------------------
1132
# Function        : SvnValidatePackageRoot
1133
#
1134
# Description     : Validate a package root
1135
#
1136
# Inputs          : $self           - Instance data
1137
#
1138
# Returns         : Will only return if valid
1139
#                   Returns a cleaned package root
1140
#
1141
sub SvnValidatePackageRoot
1142
{
379 dpurdie 1143
    my ($self, $warning_only) = @_;
267 dpurdie 1144
    Debug ("SvnValidatePackageRoot");
1145
    my $url = $self->Full || Error ("SvnValidatePackageRoot: No URL");
1146
 
1147
    Error ("Package path contains a reserved word ($self->{TAGTYPE})", "Path: $url")
1148
        if (  $self->{TAGTYPE} );
1149
 
1150
    Error ("Package name contains a Peg ($self->{PEG})", "Path: $url")
1151
        if ( $self->{PEG} );
1152
 
1153
    #
1154
    #   Ensure that the target path does exist
1155
    #   Moreover it needs to be a directory and it should have a
1156
    #   a ttb structure
1157
    #
1158
    my ( $ref_files, $ref_dirs, $ref_svn, $found ) = $self->SvnScanPath ( 'Package Base Test', $url );
1159
 
1160
    #
379 dpurdie 1161
    #   Only looking for package path
1162
    #
1163
    if ( !$found && $warning_only )
1164
    {
1165
        return $url;
1166
    }
1167
 
1168
    #
267 dpurdie 1169
    #   Examine the results to see if we have a valid package base
1170
    #
1171
    Error ("Package Base Test: Not a valid package") unless ( $found );
1172
 
1173
    #
1174
    #   Extra bits found
1175
    #   Its not the root of a package
1176
    #
1177
    if ( @$ref_files )
1178
    {
1179
        Warning ("Package Base Test: Files exists",
1180
               "Unexpected files found:", @$ref_files );
1181
    }
1182
 
1183
    #
1184
    #   Need a truck directory
1185
    #   If we don't have a truck we don't have a package
1186
    #
1187
    my $trunk_found = grep ( /trunk\//, @$ref_svn );
1188
    Error ("Invalid Package Base. Does not contain a 'trunk' directory")
1189
        unless ( $trunk_found );
1190
 
1191
    return $url;
1192
}
1193
 
1194
 
1195
#-------------------------------------------------------------------------------
1196
# Function        : SvnIsaSimpleLabel
1197
#
1198
# Description     : Check a label
1199
#                       Must not contain a PEG
1200
#                       Must not contain invalid characters (@ or /)
1201
#                       Must not contain a :: sequence (will confuse other tools)
1202
#                       Handle special label of TIMESTAMP
1203
#
1204
# Inputs          : $label          - to test
1205
#
1206
# Returns         : Will not return on error
1207
#                   Returns label on success
1208
#
1209
sub SvnIsaSimpleLabel
1210
{
1211
    my ($label) = @_;
1212
    Debug ("SvnIsaSimpleLabel, $label");
1213
 
1214
    Error ("No label provided") unless ( $label );
1215
    Error ("Invalid label. Peg (\@nnn) is not allowed: \"$label\"" ) if ( $label =~ m~@\d+$~ );
1216
    Error ("Invalid label. Package Path is not allowed: \"$label\"" ) if ( $label =~ m~/~ );
383 dpurdie 1217
    Error ("Invalid label. Invalid Start Character: \"$label\"" ) unless ( $label =~ m~^[0-9a-zA-Z]~ );
1218
    Error ("Invalid label. Invalid End Character: \"$label\"" ) unless ( $label =~ m~[0-9a-zA-Z]$~ );
267 dpurdie 1219
    Error ("Invalid label. Invalid Characters: \"$label\"" ) unless ( $label =~ m~^[-.:0-9a-zA-Z_]+$~ );
1220
    Error ("Invalid label. Double :: not allowed: \"$label\"" ) if ( $label =~m~::~ );
1221
 
1222
    #
1223
    #   Allow for a label of TIMESTAMP and have it expand
383 dpurdie 1224
    #   Create a label based on users name and a date-time that can be sorted
267 dpurdie 1225
    #
1226
    if ( $label eq 'TIMESTAMP' )
1227
    {
341 dpurdie 1228
        ::EnvImport ('USER' );
1229
        my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);
1230
        $label = sprintf("%s_%4.4u.%2.2u.%2.2u.%2.2u%2.2u%2.2u",
1231
            $::USER, $year+1900, $mon+1, $mday, $hour, $min, $sec );
267 dpurdie 1232
    }
1233
    return $label;
1234
}
1235
 
1236
#-------------------------------------------------------------------------------
1237
# Function        : NewSession
1238
#
1239
# Description     : Create a new empty SvnSession Class
1240
#
1241
# Inputs          : None
1242
#
1243
# Returns         : Class
1244
#
1245
sub NewSession
1246
{
1247
    Debug ("NewSession");
1248
    my $self  = SvnSession();
1249
 
1250
    #
1251
    #   Document Class Variables
1252
    #
1253
    $self->{URL} = '';                  # Repo URL prefix
1254
    $self->{WS}  = '';                  # Users WorkSpace
1255
    $self->{PROTOCOL} = '';             # Named Access Protocol
1256
    $self->{PKGROOT} = '';              # Package root
1257
 
1258
    #
1259
    #   Create a class
1260
    #   Bless my self
1261
    #
1262
    bless ($self, __PACKAGE__);
1263
    return $self;
1264
}
1265
 
1266
#-------------------------------------------------------------------------------
1267
# Function        : NewSessionByWS
1268
#
1269
# Description     : Establish a new SVN Session based on a Workspace
1270
#                   Given a workspace path determine the SvnServer and other
1271
#                   relevent information.
1272
#
1273
#                   Requires some rules
1274
#                       * The package is rooted within a 'ttb'
1275
#
1276
# Inputs          : $path                   - Path to WorkSpace
1277
#                   $test                   - No Error on no WS
361 dpurdie 1278
#                   $slack                  - Less stringent
267 dpurdie 1279
#
1280
# Returns         : Ref to Session Information
1281
#
1282
sub NewSessionByWS
1283
{
361 dpurdie 1284
    my ($path, $test, $slack) = @_;
267 dpurdie 1285
    Debug ("NewSessionByWS", @_);
1286
 
1287
    #
1288
    #   Create a basic Session
1289
    #   Populate it with information that is known
1290
    #
1291
    my $self = NewSession();
1292
    $self->{WS} = $path;
1293
 
1294
    #
1295
    #   Validate the path provided
1328 dpurdie 1296
    #   In the process populate $self->{InfoWs} with info about the workspace.
267 dpurdie 1297
    #
1298
    if ($self->SvnValidateWs ( undef, 1) )
1299
    {
1300
        return $self if ( $test );
1301
        Error ( @{$self->{ERROR_LIST}} );
1302
    }
1303
 
1304
    #
1305
    #   Extract useful info
1306
    #       URL: svn://auperaws996vm21/test/MixedView/trunk
1307
    #       Repository Root: svn://auperaws996vm21/test
1308
    #
1328 dpurdie 1309
    my $url = $self->{'InfoWs'}{'URL'};
1310
    my $reporoot = $self->{'InfoWs'}{'Repository Root'};
1311
    my $repoVersion = $self->{'InfoWs'}{'Revision'};
267 dpurdie 1312
 
1313
    Error ("JatsSvn Internal error. Can't parse info")
1314
        unless ( $url && $reporoot );
1315
 
1316
    #
1317
    #   Need the length of the path to the repository
1318
    #   but not the name of the repostory itself.
1319
    #
1320
    #   Remove that from the head of the URL to give a
1321
    #   path within the repository, that includes the repos name
1322
    #
1323
    $reporoot = (fileparse( $reporoot ))[1];
1324
    $url = substr ($url, length ($reporoot));
1325
    $self->{WSURL} = $url;
1326
    chop $reporoot;
1327
 
1328
    Verbose2 ("SvnLocatePackageRoot: $reporoot, $url" );
1329
 
1330
    #
1331
    #   Remove anything after a ttb ( truck, tags, branch ) element
1332
    #   This will be the root of the package within the repo
1333
    #
1334
    if (  $url =~ m~(.+)/((tags|branches|trunk)(/|$).*)~ )
1335
    {
1336
        $url = $1;
1337
        $self->{WSTYPE} = $3;
1338
    }
1339
    else
1340
    {
361 dpurdie 1341
        #
1342
        #   If we are being slack (ie deleting the workspace)
1343
        #   Then generate a warning, not an error
1344
        #
1345
        my $fnc = $slack ? \&Warning : \&Error;
1346
        $fnc->("SvnLocatePackageRoot. Non standard repository format",
1347
               "Url must contain 'tags' or 'branches' or 'trunk'",
267 dpurdie 1348
               "Url: $url");
361 dpurdie 1349
        $self->{WSTYPE} = 'trunk';
267 dpurdie 1350
    }
1351
 
1352
    #
1353
    #   Insert known information
1354
    #
1355
    $self->{URL} = $reporoot . '/';
1356
    $self->{PKGROOT} = $url;
369 dpurdie 1357
    $self->{WSREVNO} = $repoVersion;
267 dpurdie 1358
 
1359
    #
1360
    #   Create useful information
1361
    #
1362
    SplitPackageUrl($self);
1363
    return $self;
1364
}
1365
 
1366
#-------------------------------------------------------------------------------
1367
# Function        : NewSessionByUrl
1368
#
1369
# Description     : Establish a new SVN Session based on a user URL
1370
#
1371
# Inputs          : $uurl                   - Users URL
361 dpurdie 1372
#                   $ttb_test               - Test and warn for TTB structure
267 dpurdie 1373
#                   $session                - Optional: Existing session
1374
#
1375
# Returns         : Ref to Session Information
1376
#
1377
sub NewSessionByUrl
1378
{
361 dpurdie 1379
    my ($uurl, $ttb_test, $self ) = @_;
267 dpurdie 1380
    Debug ("NewSessionByUrl", @_);
1381
    Error ("No Repostory Path specified") unless ( $uurl );
1382
 
1383
    #
1384
    #   Create a basic Session
1385
    #   Populate it with information that is known
1386
    #
1387
    $self = NewSession() unless ( $self );
1388
 
1389
    #
369 dpurdie 1390
    #   Examine the URL and convert a Repository Path into a URL
353 dpurdie 1391
    #   as provided by configuration information within the environment
267 dpurdie 1392
    #
361 dpurdie 1393
    ($self->{URL}, $self->{PKGROOT} ) = SvnPath2Url ($uurl);
1394
 
1395
    #
1396
    #   Create useful information
1397
    #
1398
    SplitPackageUrl($self);
1399
 
1400
    #
1401
    #   Warn of non-standard URLs
1402
    #   These may create problems latter
1403
    #
1404
    if ( $ttb_test )
1405
    {
1406
        Warning("Non standard repository format",
1407
                 "Url should contain 'tags' or 'branches' or 'trunk'",
1408
                 "Url: $self->{PKGROOT}") unless $self->{TAGTYPE};
1409
    }
1410
 
1411
    return $self;
1412
}
1413
 
1414
#-------------------------------------------------------------------------------
1415
# Function        : SvnPath2Url
1416
#
1417
# Description     : Convert a repository path to a Full Url
1418
#                   Also handles Full Url
1419
#
1420
# Inputs          : $rpath             - Repository Path
1421
#                                        May be a full URL
1422
#
1423
# Returns         : List context
1424
#                   Two items that can be joined
1425
#                   URL                - URL
1426
#                   PKG_ROOT           - Package Root
1427
#
1428
#                   Scalar context: Joined URL and Package Root
1429
#                                   Fully formed URL
1430
#
1431
sub SvnPath2Url
1432
{
1433
    my ($rpath) = @_;
1434
    my $processed = 0;
1435
    my $url;
1436
    my $pkgroot;
1437
 
1438
    #
1439
    #   Examine the argument and convert a Repository Path into a URL
1440
    #   as provided by configuration information within the environment
1441
    #
1442
    $rpath =~ m~(.+?)/(.*)~;
1443
    my $fe = $1 || $rpath;
353 dpurdie 1444
    my $rest = $2 || '';
1445
    if ( $SVN_URLS{$fe} )
267 dpurdie 1446
    {
361 dpurdie 1447
        $url = $SVN_URLS{$fe};
1448
        $pkgroot = $rest;
353 dpurdie 1449
        $processed = 1;
341 dpurdie 1450
    }
1451
 
353 dpurdie 1452
    if ( ! $processed )
341 dpurdie 1453
    {
267 dpurdie 1454
        #
353 dpurdie 1455
        #   Examine the URL and determine if we have a FULL Url or
1456
        #   a path within the 'default' server
1457
        #
1458
        foreach my $key ( @SVN_URLS_LIST )
1459
        {
361 dpurdie 1460
            if ( $rpath =~ m~^$SVN_URLS{$key}(.*)~ )
353 dpurdie 1461
            {
361 dpurdie 1462
                $url = $SVN_URLS{$key};
1463
                $pkgroot = $1;
353 dpurdie 1464
                $processed = 1;
1465
                last;
1466
            }
1467
        }
1468
    }
267 dpurdie 1469
 
353 dpurdie 1470
    #
1471
    #   Last attempt
1472
    #   Treat as a raw URL - some operations won't be allowed
1473
    #
1474
    if ( ! $processed )
267 dpurdie 1475
    {
361 dpurdie 1476
        if ( $rpath =~ m~^((file|http|https|svn):///?([^/]+)/)(.+)~ )
353 dpurdie 1477
        {
1478
            #       http://server/
1479
            #       https://server/
1480
            #       svn://server/
1481
            #       file://This/Isa/Bad/Guess
1482
            #
361 dpurdie 1483
            $url = $1;
1484
            $pkgroot = $4;
353 dpurdie 1485
        }
369 dpurdie 1486
        elsif ($SVN_URLS{''} )
353 dpurdie 1487
        {
1270 dpurdie 1488
            if ( exists $ENV{'GBE_ABT'} && $ENV{'GBE_ABT'})
1489
            {
1490
                Error ("Attempt to use default repository within automated build", "Path: " . $rpath);
1491
            }
361 dpurdie 1492
            $url = $SVN_URLS{''};
1493
            $pkgroot = $rpath;
353 dpurdie 1494
        }
1495
        else
1496
        {
1497
            #
1498
            #   User default (site configured) Repo Root
1499
            #
1500
            Error ("No site repository configured for : $fe",
1501
                   "Configure GBE_SVN_URL_" . uc($fe) );
1502
        }
267 dpurdie 1503
    }
1504
 
1505
    #
361 dpurdie 1506
    #   May want two elements, may want one
267 dpurdie 1507
    #
361 dpurdie 1508
    return $url, $pkgroot if ( wantarray );
1509
    return $url . $pkgroot;
267 dpurdie 1510
}
1511
 
369 dpurdie 1512
#-------------------------------------------------------------------------------
1513
# Function        : SvnPaths
1514
#
1515
# Description     : Extract SVN path conversion information
1516
#
1517
# Inputs          : Nothing
1518
#
1519
# Returns         : Two refs
1520
#                   Hash of SVN URLS
1521
#                   Array for search order
1522
#
1523
sub SvnPaths
1524
{
1525
    return \%SVN_URLS, \@SVN_URLS_LIST;
1526
}
361 dpurdie 1527
 
267 dpurdie 1528
#-------------------------------------------------------------------------------
1529
# Function        : SplitPackageUrl
1530
#
1531
# Description     : Slip the package URL into a few useful bits
1532
#
1533
# Inputs          : $self           - Instance data
1534
#
1535
# Returns         : Nothing
1536
#
1537
sub SplitPackageUrl
1538
{
1539
    my ($self) = @_;
353 dpurdie 1540
    Debug ("SplitPackageUrl", $self->{URL}, $self->{PKGROOT});
267 dpurdie 1541
 
1542
    #
1543
    #   Remove any protocol that may be present
1544
    #       http://server/
341 dpurdie 1545
    #       https://server/
267 dpurdie 1546
    #       svn://server/
1547
    #       file://This/Isa/Bad/Guess
1548
    #
341 dpurdie 1549
    if ( $self->{URL} =~ m~^(file|http|https|svn)://([^/]+)~ )
267 dpurdie 1550
    {
1551
        $self->{PROTOCOL} = $1;
1552
        $self->{SERVER} = $2;
1553
    }
1554
 
1555
    if ( $self->{PKGROOT} =~ m~(.*)(@\d+)$~ )
1556
    {
1557
        $self->{PEG} = $2;
1558
    }
1559
 
1560
    #
1561
    #   Determine TTB type
1562
    #   Need to handle
1563
    #       .../trunk
1564
    #       .../trunk@nnnnn
1565
    #       .../tags/version@nnnnn
1566
    #       .../branches/version@nnnnn
1567
    #
1568
    #
1569
    if (  $self->{PKGROOT} =~ m~/?(.*)/(tags|branches|trunk)(/|$|@)(.*)$~ )
1570
    {
1571
        $self->{PATH}         = $1;
1572
        $self->{TAGTYPE}      = $2;
1573
        $self->{VERSION}      = $4;
1574
    }
1575
    else
1576
    {
1577
        $self->{PATH} = $self->{PKGROOT};
1578
    }
1579
 
1580
    DebugDumpData ('SplitPackageUrl', $self ) if ( IsDebug(2) );
1581
}
1582
 
1583
#-------------------------------------------------------------------------------
1584
# Function        : Full
1585
#                   FullWs
1586
#                   Repo
1587
#                   Peg
1588
#                   Type
1589
#                   WsType
1590
#                   Path
1591
#                   Version
1592
#                   RmRef
385 dpurdie 1593
#                   RmPath
267 dpurdie 1594
#
1595
# Description     : Accessor functions
1596
#
1597
# Inputs          : $self       - Instance data
1598
#                                 self (is $_[0])
1599
#
1600
# Returns         : Data Item
1601
#
369 dpurdie 1602
sub Full        { return $_[0]->{URL} . $_[0]->{PKGROOT} ; }
1603
sub FullWs      { return $_[0]->{URL} . $_[0]->{WSURL} ; }
1604
sub FullWsRev   { return $_[0]->{URL} . $_[0]->{WSURL} . '@' . $_[0]->{WSREVNO} ; }
1605
sub Peg         { return $_[0]->{PEG} ; }
1606
sub Type        { return $_[0]->{TAGTYPE} || '' ; }
1607
sub WsType      { return $_[0]->{WSTYPE}  || '' ; }
1608
sub Path        { return $_[0]->{PATH} ; }
1609
sub Version     { return $_[0]->{VERSION} ; }
1610
sub RmRef       { return $_[0]->{RMREF} ; }
385 dpurdie 1611
sub RmPath      { my $path = $_[0]->{RMREF}; $path =~ s~@.*?$~~ ;return  $path; }
267 dpurdie 1612
 
1613
#-------------------------------------------------------------------------------
1614
# Function        : Print
1615
#
1616
# Description     : Debug display the URL
1617
#
1618
# Inputs          : $self           - Instance data
1619
#                   $header
1620
#                   $indent
1621
#
1622
# Returns         : Nothing
1623
#
1624
sub Print
1625
{
1626
    my ($self, $header, $indent) = @_;
1627
    print "$header\n" if $header;
1628
    $indent = 4 unless ( defined $indent );
1629
    $indent = ' ' x $indent;
1630
 
1631
 
1632
    print $indent . "PROTOCOL:" . $self->{PROTOCOL} . "\n";
1633
    print $indent . "SERVER  :" . $self->{SERVER} . "\n";
1634
    print $indent . "URL     :" . $self->{URL} . "\n";
1635
    print $indent . "PKGROOT :" . $self->{PKGROOT} . "\n";
1636
    print $indent . "PATH    :" . $self->{PATH} . "\n";
353 dpurdie 1637
    print $indent . "TAGTYPE :" . ($self->{TAGTYPE} || '') . "\n";
1638
    print $indent . "VERSION :" . ($self->{VERSION} || '') . "\n";
1639
    print $indent . "PEG     :" . ($self->{PEG} || '') . "\n";
267 dpurdie 1640
    print $indent . "FULL    :" . $self->Full . "\n";
1641
}
1642
 
1643
#-------------------------------------------------------------------------------
1644
# Function        : BranchName
1645
#
1646
# Description     : Create a full URL to a branch or tag based on the
1647
#                   current entry
1648
#
1649
#                   URL must have a TTB format
1650
#
1651
# Inputs          : $self           - Instance data
1652
#                   $branch         - Name of the branch
1653
#                   $type           - Optional branch type
1654
#
1655
# Returns         : Full URL name to the new branch
1656
#
1657
sub BranchName
1658
{
1659
    my ($self, $branch, $type ) = @_;
1660
    Debug ( "BranchName", $branch );
1661
 
1662
    $type = 'branches' unless ( $type );
1663
    my $root = $self->{PKGROOT};
1664
 
1665
    $root =~ s~/(tags|branches|trunk)(/|$|@).*~~;
1666
 
1667
    return $self->{URL} . $root . '/' . $type . '/' . $branch;
1668
}
1669
 
379 dpurdie 1670
#-------------------------------------------------------------------------------
1671
# Function        : setRepoProperty
1672
#
1673
# Description     : Sets a Repository property
1674
#                   This may well fail unless the Repo is setup to allow such
1675
#                   chnages and the user is allowed to make such changes
1676
#
1677
# Inputs          : $name
1678
#                   $value
1341 dpurdie 1679
#                   $allowError     - Support for bad repositories
379 dpurdie 1680
#
1341 dpurdie 1681
# Returns         : 0 - Change made
1682
#                   Will not return on error
379 dpurdie 1683
#
1684
sub setRepoProperty
1685
{
1341 dpurdie 1686
    my ($self, $name, $value, $allowError ) = @_;
1687
    my $retval = 0;
1688
 
379 dpurdie 1689
    Debug ( "setRepoProperty", $name, $value );
1690
    #
1691
    #   Ensure that the Repo version is known
1692
    #   This should be set by a previous operation
1693
    #
1694
    unless ( defined $self->{REVNO} )
1695
    {
1696
        Error ("setRepoProperty. Release Revision Number not known");
1697
    }
1698
 
1699
    #
1700
    #   Execute the command
1701
    #
1702
    if ( $self->SvnCmd ( 'propset' , $name, '--revprop', '-r',  $self->{REVNO}, $value, $self->Full,
1703
                            {
1704
                                'credentials' => 1,
1705
                                'nosavedata' => 1,
1706
                            }
1707
                       ) )
1708
    {
1709
        #
1710
        #   Property NOT set
1711
        #
1341 dpurdie 1712
        if ( $allowError )
1713
        {
1714
            Warning ("setRepoProperty: $name - FAILED");
1715
            $retval = 1;
1716
        }
1717
        else
1718
        {
1719
            Error ("setRepoProperty: $name - FAILED");
1720
        }
379 dpurdie 1721
    }
1341 dpurdie 1722
 
1723
    return $retval;
379 dpurdie 1724
}
1725
 
1341 dpurdie 1726
 
1727
#-------------------------------------------------------------------------------
1728
# Function        : backTrackSvnLabel
1729
#
1730
# Description     : Examine a Svn Tag and backtrack until we find the branch
1731
#                   that was used to create the label
1732
#
1733
# Inputs          : $self                   - Instance Data
1734
#                   $src_label              - Label to process
1735
#                                             Label within the current instance
1736
#
1737
# Returns         : Branch from which the label was taken
1738
#                   or the label prefixed with 'tags'.
1739
#
1740
sub backTrackSvnLabel
1741
{
1742
    my ($self, $src_label) = @_;
1743
 
1744
    #
1745
    #   Init stored data
1746
    #
1747
    $self->{btData} = ();
1748
 
1749
    #
1750
    #   extract data
1751
    #
1752
#    DebugDumpData("SVN", $svn );
1753
#$self->{PRINTDATA} = 1;
1754
    $src_label =~ s~^tags/~~;
1755
    $self->SvnCmd ( 'log', '-v', '--xml', '-q', '--stop-on-copy', $self->Full() . '/tags/' . $src_label
1756
                    , { 'credentials' => 1,
1757
                        'process' => \&ProcessBackTrack,
1758
                         }
1759
                        );
1760
 
1761
    #
1762
    #   Process data
1763
    #       $self->{btData}{entry}      - temp stuff
1764
    #       @{$self->{btData}{data}}    - Array of entries
1765
    #
1766
    #
1767
#    DebugDumpData("btData", $self->{btData} );
1768
 
1769
    my $peglessLabel = $src_label;
1770
       $peglessLabel =~ s~@\d+$~~;
1771
    my $branch;
1772
    foreach my $entry ( @{$self->{btData}{data}} )
1773
    {
1774
        my $name;
1775
        my $isaBranch;
1776
        my $target = $entry->{target};
1777
 
1778
        if ( $target =~ m~/tags/$peglessLabel$~ )
1779
        {
1780
            my $parent = $entry->{fromPath};
1781
            if ( $parent =~ m~(.+)/((tags|branches|trunk)(/|$).*)~ )
1782
            {
1783
                $branch = $2 . '@' . $entry->{fromRev};
1784
                last;
1785
            }
1786
        }
1787
    }
1788
    delete $self->{btData};
1789
 
1790
    #
1791
    #   Return nice value or original value
1792
    #
1793
    unless ( $branch )
1794
    {
1795
        $branch = 'tags/' . $src_label;
1796
    }
1797
 
1798
    Verbose( "backTrackSvnLabel: $src_label -> $branch");
1799
    return $branch;
1800
}
1801
 
1802
#-------------------------------------------------------------------------------
1803
# Function        : ProcessBackTrack
1804
#
1805
# Description     :
1806
#                   Parse
1807
#                       <logentry
1808
#                          revision="24272">
1809
#                       <author>bivey</author>
1810
#                       <date>2005-07-25T15:45:35.000000Z</date>
1811
#                       <paths>
1812
#                       <path
1813
#                          prop-mods="false"
1814
#                          text-mods="false"
1815
#                          kind="dir"
1816
#                          copyfrom-path="/enqdef/branches/Stockholm"
1817
#                          copyfrom-rev="24271"
1818
#                          action="A">/enqdef/tags/enqdef_24.0.1.sls</path>
1819
#                       </paths>
1820
#                       <msg>COTS/enqdef: Tagged by Jats Svn Import</msg>
1821
#                       </logentry>
1822
#
1823
#
1824
#                   Uses:   $self->{btData}     - Scratch Data
1825
#
1826
# Inputs          : $self           - Class Data
1827
#                   $line           - Input data to parse
1828
#
1829
# Returns         : 0 - Do not terminate input command
1830
#
1831
sub  ProcessBackTrack
1832
{
1833
    my ($self, $line ) = @_;
1834
    Message ( $line ) if $self->{PRINTDATA};
1835
 
1836
    if ( $line =~ m~^<logentry~ ) {
1837
        $self->{btData}{entry} = ();
1838
 
1839
    } elsif ( $line =~ m~^\s+revision="(\d+)"~ ) {
1840
        $self->{btData}{entry}{Rev} = $1;
1841
 
1842
    } elsif ( $line =~ m~^\s+copyfrom-path="(.*)"~ ) {
1843
        $self->{btData}{entry}{fromPath} = $1;
1844
 
1845
    } elsif ( $line =~ m~^\s+copyfrom-rev="(\d+)"~ ) {
1846
        $self->{btData}{entry}{fromRev} = $1;
1847
 
1848
    } elsif ( $line =~ m~\s+action=.*?>(.*)</path~ ) {
1849
        $self->{btData}{entry}{target} = $1;
1850
 
1851
    } elsif ( $line =~ m~</logentry~ ) {
1852
        if ( exists $self->{btData}{entry}{fromPath} )
1853
        {
1854
#            DebugDumpData("Data", $self->{btData}{entry};
1855
            push @{$self->{btData}{data}}, $self->{btData}{entry};
1856
        }
1857
    }
1858
 
1859
    #
1860
    #   Return 0 to keep on going
1861
    return 0;
1862
}
1863
 
1864
 
1865
 
267 dpurdie 1866
#------------------------------------------------------------------------------
1867
1;