File Coverage

blib/lib/ClearCase/SyncTree.pm
Criterion Covered Total %
statement 60 1042 5.7
branch 2 514 0.3
condition 0 164 0.0
subroutine 18 74 24.3
pod 15 48 31.2
total 95 1842 5.1


line stmt bran cond sub pod time code
1             package ClearCase::SyncTree;
2              
3             $VERSION = '0.60';
4              
5             require 5.004;
6              
7 1     1   918 use strict;
  1         2  
  1         38  
8              
9 1     1   5 use Cwd;
  1         1  
  1         77  
10 1     1   6 use File::Basename;
  1         5  
  1         136  
11 1     1   1081 use File::Compare;
  1         1324  
  1         60  
12 1     1   1016 use File::Copy;
  1         6080  
  1         83  
13 1     1   8 use File::Find;
  1         2  
  1         60  
14 1     1   5 use File::Path;
  1         10  
  1         68  
15 1     1   5 use File::Spec 0.82;
  1         30  
  1         29  
16 1     1   1055 use ClearCase::Argv 1.34 qw(chdir);
  1         51628  
  1         120  
17              
18 1 50   1   11 use constant MSWIN => $^O =~ /MSWin|Windows_NT/i ? 1 : 0;
  1         2  
  1         110  
19 1 50   1   5 use constant CYGWIN => $^O =~ /cygwin/i ? 1 : 0;
  1         2  
  1         559  
20              
21             my $lext = '.=lnk='; # special extension for pseudo-symlinks
22              
23             sub new {
24 0     0 0 0 my $proto = shift;
25 0         0 my $class;
26 0 0       0 if ($class = ref($proto)) {
27             # Make a (deep) clone of the invoking instance
28 0         0 require Clone;
29 0         0 Clone->VERSION(0.12); # 0.10 has a known bug
30 0         0 return Clone::clone($proto);
31             }
32 0         0 $class = $proto;
33 0         0 my $self = {@_};
34 0         0 bless $self, $class;
35 0         0 $self->comment('By:' . __PACKAGE__);
36             # Default is to sync file modes unless on ^$%#* Windows.
37 0         0 $self->protect(1);
38             # Set up a ClearCase::Argv instance with the appropriate attrs.
39 0         0 $self->ct;
40             # By default we'll call SyncTree->fail on any cleartool error.
41 0         0 $self->err_handler($self, 'fail');
42             # Set default file comparator.
43 0         0 $self->cmp_func(\&File::Compare::compare);
44 0         0 return $self;
45             }
46              
47             sub err_handler {
48 0     0 1 0 my $self = shift;
49 0         0 my $ct = $self->ct;
50 0 0       0 if (@_ >= 2) {
51 0         0 my($obj, $method) = @_;
52 0 0       0 $method = join('::', ref($obj), $method) unless $method =~ /::/;
53 0         0 $ct->autofail([\&$method, $obj]);
54             } else {
55 0         0 $ct->autofail(@_);
56             }
57             }
58              
59             # For internal use only. Provides a std msg format.
60             sub _msg {
61 0     0   0 my $prog = basename($0);
62 0         0 my $type = shift;
63 0         0 my $msg = "@_";
64 0         0 chomp $msg;
65 0         0 return "$prog: $type: $msg\n";
66             }
67              
68             # For internal use only. A synonym for die() with a std error msg format.
69             sub fatal {
70 0     0 0 0 die _msg('Error', @_);
71             }
72              
73             # For internal use only. A synonym for warn() with a std error msg format.
74             sub warning {
75 0     0 0 0 warn _msg('Warning', @_);
76             }
77              
78             # For internal use only. Returns the ClearCase::Argv object.
79             sub ct {
80 0     0 0 0 my $self = shift;
81 0 0       0 return $self->{ST_CT} if $self->{ST_CT};
82 0 0       0 if (!defined(wantarray)) {
83 0         0 my $ct = ClearCase::Argv->new({autochomp=>1, outpathnorm=>1});
84 0         0 $ct->syxargs($ct->qxargs);
85 0         0 $self->{ST_CT} = $ct;
86             }
87 0         0 return $self->{ST_CT};
88             }
89              
90             # For internal use only. Returns a clone of the ClearCase::Argv object.
91             sub clone_ct {
92 0     0 0 0 my $self = shift;
93 0         0 my $ct = $self->ct->clone(@_);
94 0 0 0     0 my $af = $self->ct->autofail
      0        
95             unless $_[0] and (ref($_[0]) eq 'HASH') and exists $_[0]->{autofail};
96 0 0 0     0 $ct->autofail($af) if $af && ref($af); #Cloning doesn't share the value
97 0         0 return $ct;
98             }
99              
100             sub gen_accessors {
101 1     1 0 5 my @key = map {uc} @_;
  14         27  
102 1     1   6 no strict 'refs';
  1         2  
  1         151  
103 1         5 for (@key) {
104 14         26 my $var = "ST_$_";
105 14         27 my $meth = lc;
106             *$meth = sub {
107 0     0   0 my $self = shift;
108 0 0       0 $self->{$var} = shift if @_;
109 0         0 return $self->{$var};
110             }
111 14         93 }
112             }
113             gen_accessors(qw(protect remove reuse vreuse lblver ignore_co overwrite_co
114             snapdest ctime lbtype inclb cmp_func rellinks dstview));
115             sub gen_flags {
116 1     1 0 3 my @key = map {uc} @_;
  3         10  
117 1     1   5 no strict 'refs';
  1         3  
  1         978  
118 1         4 for (@key) {
119 3         10 my $var = "ST_$_";
120 3         6 my $meth = lc;
121             *$meth = sub {
122 0     0     my $self = shift;
123 0 0 0       $self->{$var} = 1 if $_[0] || !defined(wantarray);
124 0           return $self->{$var};
125             }
126 3         24 }
127             }
128             gen_flags(qw(label_mods no_cr no_cmp));
129              
130             sub comment {
131 0     0 1   my $self = shift;
132 0           my $cmnt = shift;
133 0 0         if (ref $cmnt) {
    0          
134 0           $self->{ST_COMMENT} = $cmnt;
135             } elsif ($cmnt) {
136 0           $self->{ST_COMMENT} = ['-c', $cmnt];
137             }
138 0           return $self->{ST_COMMENT};
139             }
140              
141             sub normalize {
142 0     0 0   my $self = shift;
143 0           chomp(my $path = shift);
144 0           my $dv = $self->dstview;
145 0           my $md = $self->mvfsdrive if MSWIN;
146 0           for ($path) {
147 0           if (MSWIN) {
148             s%^$md:%%;
149             s%^[\\/]\Q$dv%%;
150             s%\\%/%g;
151             $_ = "$md:/$dv$_";
152             } elsif (CYGWIN) {
153             # 4 cases: unc; /view/x user mount; view drive; mvfs drive/tag
154             s%^/(/?view/$dv|cygdrive/\w(/$dv)?)%%;
155             $_ = "//view/$dv$_";
156             } else {
157 0           s%^/view/$dv%%;
158 0           $_ = "/view/$dv$_";
159             }
160 0           s%/\.?$%%;
161             }
162 0           return $path;
163             }
164              
165             sub canonicalize {
166 0     0 0   my $self = shift;
167 0           my $base = shift;
168 0           for (@_) {
169 0 0 0       $_ = File::Spec->canonpath(join('/', $base, $_))
170             if $_ && ! File::Spec->file_name_is_absolute($_);
171             }
172             }
173              
174             # Returns -other and -do private files. Checkouts are handled separately.
175             sub _lsprivate {
176 0     0     my $self = shift;
177 0           my $implicit_dirs = shift;
178 0           my $base = $self->dstbase;
179 0           my $dv = $self->dstview;
180 0           my $ct = $self->clone_ct({autofail=>0, stderr=>0});
181 0           my @vp;
182 0           for ($ct->argv('lsp', [qw(-oth -do -s -inv), "$base/.", '-tag', $dv])->qx) {
183 0           $_ = $self->normalize($_);
184 0 0         push(@vp, $_) if m%^\Q$base/%;
185             }
186 0           push(@vp, grep {$ct->des([qw(-s)], "$_/.\@\@")->stdout(0)->system}
  0            
187 0 0 0       @{$self->{ST_IMPLICIT_DIRS}})
188             if $self->{ST_IMPLICIT_DIRS} && $implicit_dirs;
189 0           return @vp;
190             }
191              
192             sub _lsco {
193 0     0     my $self = shift;
194 0           my $base = $self->_mkbase;
195 0           my $ct = $self->clone_ct;
196 0           my $sil = $self->clone_ct(stderr=>0, autofail=>0);
197 0           my %co;
198 0           for ($ct->lsco([qw(-s -cvi -a)], $base)->qx) {
199 0           $_ = $self->normalize($_);
200 0 0 0       $co{$_}++ if m%^\Q$base/% || $_ eq $base;
201             }
202 0           for my $dir (@{$self->{ST_IMPLICIT_DIRS}}) {
  0            
203 0           my $dad = dirname($dir);
204 0 0         $co{$dad}++ if $sil->lsco([qw(-s -cvi -d)], $dad)->qx;
205             }
206 0 0         return wantarray? sort keys %co : scalar keys %co;
207             }
208              
209             sub mvfsdrive {
210 0     0 0   my $self = shift;
211 0           if (MSWIN && ! $self->{ST_MVFSDRIVE}) {
212 1     1   17 no strict 'subs';
  1         3  
  1         35  
213 1     1   6 use vars '$Registry';
  1         2  
  1         1151  
214             require Win32::TieRegistry;
215             # HKLM is read-only for non-admins so open read-only
216             Win32::TieRegistry->import('TiedRef', '$Registry', qw(KEY_READ));
217             my $LMachine = $Registry->Open('LMachine', {Access => KEY_READ});
218             $self->{ST_MVFSDRIVE} = $LMachine->{SYSTEM}->
219             {CurrentControlSet}->{Services}->{Mvfs}->{Parameters}->{drive};
220             die "$0: Error: unable to find MVFS drive" unless $self->{ST_MVFSDRIVE};
221             }
222 0           return $self->{ST_MVFSDRIVE};
223             }
224              
225             sub ccsymlink {
226 0     0 0   my $dst = shift;
227 0 0         return 1 if -l $dst;
228 0           return 0 unless MSWIN || CYGWIN;
229 0           my $ct = new ClearCase::Argv({autochomp=>1, stderr=>0});
230 0           return $ct->des([qw(-fmt %m)], $dst)->qx eq 'symbolic link';
231             }
232              
233             # readlink might work under some conditions (CC version, mount options, ...)
234             sub readcclink {
235 0     0 0   my $dst = shift;
236 0           my $ret = readlink $dst;
237 0 0 0       return $ret if $ret || !(MSWIN || CYGWIN);
238 0           my $ct = new ClearCase::Argv({autochomp=>1});
239 0           $ret = $ct->ls($dst)->qx;
240 0           $ret =~ s%\\%/%g if MSWIN;
241 0 0         return (($ret =~ s/^.*? --> (.*)$/$1/)? $ret : '');
242             }
243              
244             sub srcbase {
245 0     0 1   my $self = shift;
246 0 0         if (@_) {
247 0           my $sbase = File::Spec->rel2abs(shift);
248 0           $sbase =~ s%\\%/%g; # rel2abs forces native (\) separator
249 0           $sbase =~ s%/\.$%%; # workaround for bug in File::Spec 0.82
250             # File::Spec::Win32::rel2abs leaves trailing / on drive letter root.
251 0 0         $sbase =~ s%/*$%% if $sbase ne '/';
252 0           $self->{ST_SRCBASE} = $sbase;
253 0     0     *src_slink = sub { return -l shift };
  0            
254 0     0     *src_rlink = sub { return readlink shift };
  0            
255 0           if (MSWIN || CYGWIN) {
256             my $ct = $self->clone_ct({autofail=>1, autochomp=>1});
257             my $olddir = getcwd;
258             $ct->_chdir($sbase) || die "$0: Error: $sbase: $!";
259             if ($ct->pwv(['-s'])->qx !~ /\s+NONE\s+/) {
260             *src_slink = \&ccsymlink;
261             *src_rlink = \&readcclink;
262             }
263             $ct->_chdir($olddir);
264             }
265             }
266 0           return $self->{ST_SRCBASE};
267             }
268              
269             sub dstbase {
270 0     0 1   my $self = shift;
271 0 0         if (@_) {
272 0           my $dbase = shift;
273 0 0 0       -e $dbase || mkpath($dbase, 0, 0777) || die "$0: Error: $dbase: $!";
274 0           my $ct = $self->clone_ct({autofail=>1, autochomp=>1});
275 0           my $olddir = getcwd;
276 0 0         $ct->_chdir($dbase) || die "$0: Error: $dbase: $!";
277 0           $dbase = getcwd;
278 0           my $dv = $ct->pwv(['-s'])->qx;
279 0 0 0       die "$0: Error: destination base ($dbase) not in a view/VOB context"
280             if !$dv || $dv =~ m%\sNONE\s%;
281 0           $self->dstview($dv);
282             # We need to derive the current vob of the dest path, which we
283             # do by cd-ing there temporarily and running "ct desc -s vob:.".
284             # But with a twist because of @%$*&# Windows.
285 0           my $dvob;
286 0 0         if (!($dvob = $self->dstvob)) {
287             # We need this weird hack to get a case-correct version of the
288             # dest path, in case the user typed it in random case. There
289             # appears to be a bug in CC 4.2; "ct desc vob:foo" fails if
290             # "foo" is not the right case even if MVFS is set to be
291             # case insensitive. This is caseid v0869595, bugid CMBU00055321.
292             # Since Windows mount points must be at the root level,
293             # we assume the vob tag must be the root dir name. We must
294             # still then look that up in lsvob to get the tag case right.
295 0           if (MSWIN) {
296             my @vobs = $ct->lsvob(['-s'])->qx;
297             my $dirpart = (File::Spec->splitpath($dbase, 1))[1];
298             for my $name (File::Spec->splitdir($dirpart)) {
299             last if $dvob;
300             next unless $name;
301             for my $vob (@vobs) {
302             if ($vob =~ m%^[/\\]$name$%i) {
303             ($dvob = $vob) =~ s%\\%/%g;
304             last;
305             }
306             }
307             }
308             } else {
309 0           $dvob = $ct->desc(['-s'], "vob:.")->qx;
310             }
311 0           $self->dstvob($dvob);
312             }
313             # On Windows, normalize the specified dstbase to use the
314             # MVFS drive (typically M:), e.g. M:\view-name\vob-tag\path...
315             # This avoids all kinds of problems with using the view
316             # via a different drive letter or a UNC (\\view) path.
317             # Similarly, on UNIX we normalize to a view-extended path
318             # even if we're already in a set view because it's the
319             # lowest common denominator. Also, if the set view differs
320             # from the 'dest view', the dest view should win.
321 0           if (MSWIN) {
322             $dbase =~ s%\\%/%g;
323 1     1   5 use vars '%RegHash';
  1         2  
  1         10995  
324             require Win32::TieRegistry;
325             Win32::TieRegistry->import('TiedHash', '%RegHash');
326             my $mdrive = $self->mvfsdrive;
327             $dbase = getcwd;
328             $dbase =~ s%.*?$dvob%$mdrive:/$dv$dvob%i;
329             } else {
330 0           $dbase = getcwd;
331 0           if (CYGWIN) {
332             $dbase =~ s%^/(/?view/$dv|cygdrive/\w)%%;
333             $dbase = "//view/$dv$dbase";
334             } else {
335 0           $dbase =~ s%^/view/$dv%%;
336 0           $dbase = "/view/$dv$dbase";
337             }
338             }
339 0 0         $ct->_chdir($olddir) || die "$0: Error: $olddir: $!";
340 0           $self->{ST_DSTBASE} = $dbase;
341 0           (my $dvb = $dbase) =~ s%^(.*?$dvob).*$%$1%;
342 0 0         $self->snapdest(1) unless -e "$dvb/@@";
343             }
344 0           return $self->{ST_DSTBASE};
345             }
346              
347             # We may have created a view-private parent tree, so must
348             # work our way upwards till we get to a versioned dir.
349             sub _mkbase {
350 0     0     my $self = shift;
351 0 0         if (! $self->{ST_MKBASE}) {
352 0           my $mbase = $self->dstbase;
353 0           my $dvob = $self->dstvob;
354 0           (my $dext = $mbase) =~ s%(.*?$dvob)/.*%$1%;
355 0           my $ct = $self->clone_ct({stdout=>0, stderr=>0, autofail=>0});
356 0           while (1) {
357 0 0         last if length($mbase) <= length($dext);
358 0 0 0       last if -d $mbase && ! $ct->desc(['-s'], "$mbase/.@@")->system;
359 0           push(@{$self->{ST_IMPLICIT_DIRS}}, $mbase);
  0            
360 0           $mbase = dirname($mbase);
361             }
362 0           $self->{ST_MKBASE} = $mbase;
363             }
364 0           return $self->{ST_MKBASE};
365             }
366              
367             sub dstvob {
368 0     0 0   my $self = shift;
369 0 0         if (@_) {
370 0           $self->{ST_DSTVOB} = shift;
371 0           $self->{ST_DSTVOB} =~ s%\\%/%g;
372             }
373 0           return $self->{ST_DSTVOB};
374             }
375              
376             sub srclist {
377 0     0 1   my $self = shift;
378 0 0         my $type = ref($_[0]) ? ${shift @_} : 'NORMAL';
  0            
379 0           my $sbase = $self->srcbase;
380 0 0         die "$0: Error: must specify src base before src list" if !$sbase;
381 0           for (@_) {
382 0 0         next if $_ eq $sbase;
383 0 0         if (m%^(?:[a-zA-Z]:)?$sbase[/\\]*(.+)%) {
    0          
384 0           $self->{ST_SRCMAP}->{$1}->{type} = $type;
385             } elsif (-e "$sbase/$_") {
386 0           $self->{ST_SRCMAP}->{$_}->{type} = $type;
387             } else {
388 0           warn "Warning: $_: no such file or directory\n";
389             }
390             }
391             }
392              
393             sub srcmap {
394 0     0 1   my $self = shift;
395 0 0         my $type = ref($_[0]) ? ${shift @_} : 'NORMAL';
  0            
396 0           my %sdmap = @_;
397 0           my $sbase = $self->srcbase;
398 0           my $dbase = $self->dstbase;
399 0 0         die "$0: Error: must specify src base before src map" if !$sbase;
400 0 0         die "$0: Error: must specify dst base before src map" if !$dbase;
401 0           for (keys %sdmap) {
402 0 0         if (m%^(?:[a-zA-Z]:)?\Q$sbase\E[/\\]*(.*)$%) {
    0          
    0          
403 0           my $key = $1;
404 0           $self->{ST_SRCMAP}->{$key}->{type} = $type;
405 0           my($dst) = ($sdmap{$_} =~ m%^\Q$dbase\E[/\\]*(.+)$%);
406 0           $self->{ST_SRCMAP}->{$key}->{dst} = $dst;
407             } elsif (-e $_) {
408 0           $self->{ST_SRCMAP}->{$_}->{type} = $type;
409 0 0         if ($sdmap{$_} =~ m%^\Q$dbase\E[/\\]*(.+)$%) {
410 0           $self->{ST_SRCMAP}->{$_}->{dst} = $1;
411             } else {
412 0           $self->{ST_SRCMAP}->{$_}->{dst} = $sdmap{$_};
413             }
414             } elsif (-e "$sbase/$_") {
415 0           $self->{ST_SRCMAP}->{$_}->{type} = $type;
416 0           $self->{ST_SRCMAP}->{$_}->{dst} = $sdmap{$_};
417             } else {
418 0           warn "Warning: $_: no such file or directory\n";
419             }
420             }
421             }
422              
423             sub eltypemap {
424 0     0 1   my $self = shift;
425 0 0         %{$self->{ST_ELTYPEMAP}} = @_ if @_;
  0            
426 0 0         return $self->{ST_ELTYPEMAP} ? %{$self->{ST_ELTYPEMAP}} : ();
  0            
427             }
428              
429             sub dstcheck {
430 0     0 0   my $self = shift;
431 0           my $dbase = $self->dstbase;
432 0 0         die "$0: Error: must specify dest base before dstcheck" if !$dbase;
433 0           my @existing = ();
434 0 0         if (-e $dbase) {
435             # Check for view private files under the dest base.
436 0           my @vp = $self->_lsprivate(0);
437 0           my $n = @vp;
438 0 0         my $s = $n == 1 ? '' : 's';
439 0 0         my $es = $n == 1 ? 's' : '';
440 0 0         die "$0: Error: $n view-private file$s exist$es under $dbase:\n @vp\n"
441             if @vp;
442             # Check for checkouts under the dest base.
443 0           @existing = $self->_lsco;
444 0           $n = @existing;
445 0 0         $s = $n >= 2 ? 's' : '';
446 0 0         if ($n == 0) {
    0          
    0          
447             # do nothing
448             } elsif ($self->ignore_co) {
449 0           warning "skipping $n checkout$s under $dbase";
450             } elsif ($self->overwrite_co) {
451 0           warning "overwriting $n checkout$s under $dbase";
452             } else {
453 0           fatal("$n checkout$s found under $dbase");
454             }
455             }
456 0           $self->{ST_PRE} = { map {$_ => 1} @existing };
  0            
457             }
458              
459             # Comparator function used to implement the -vreuse option
460             # If the default comparaison fails, look at versions of suitable size
461             # in the version tree, and apply the comparaison to them.
462             # If a suitable version is found, add it to a list of versions on which
463             # to apply a label.
464             sub vtcomp {
465 0     0 0   my($self, $src, $dst) = @_;
466 0           my $cmp = $self->cmp_func;
467 0           my $lb = $self->lblver;
468 0 0         if ($lb) {
469 0           my $lblver = "$dst\@\@/$lb";
470 0 0         $dst = $lblver if -r $lblver;
471             }
472 0 0         return 0 unless $cmp->($src, $dst);
473 0           my $vt = ClearCase::Argv->lsvtree([qw(-a -s -nco)]);
474 0           my @vt = reverse grep {m%[\\/]\d*$%} $vt->args($dst)->qx;
  0            
475 0           chomp @vt;
476 0           my $sz = -s $src;
477 0           for (@vt) {
478 0 0         next if -s $_ != $sz;
479 0 0         if (!$cmp->($src, $_)) {
480 0           push @{$self->{ST_LBL}}, $_;
  0            
481 0           return 0;
482             }
483             }
484 0           return 1;
485             }
486              
487             sub _needs_update {
488 0     0     my($self, $src, $dst, $comparator) = @_;
489 0           my $update = 0;
490 0 0 0       if (src_slink($src) && ccsymlink($dst)) {
    0 0        
491 0           my $srctext = src_rlink($src);
492 0           my $desttext = readcclink $dst;
493 0   0       $update = !defined($comparator) || ($srctext ne $desttext);
494             } elsif (! src_slink($src) && ! ccsymlink($dst)) {
495 0 0         if (!defined($comparator)) {
    0          
    0          
496 0           $update = 1;
497             } elsif ($self->vreuse) {
498 0           $update = $self->vtcomp($src, $dst);
499             } elsif (-s $src != -s $dst) {
500 0           $update = 1;
501             } else {
502 0           $update = &$comparator($src, $dst);
503             }
504 0 0         $self->failm("failed comparing $src vs $dst: $!") if $update < 0;
505             } else {
506 0           $update = 1;
507             }
508 0 0 0       if ($update && (!exists($self->{ST_PRE}->{$dst}) || $self->overwrite_co)) {
      0        
509 0           return 1;
510             } else {
511 0           return 0;
512             }
513             }
514              
515             sub checkcs {
516 0     0 0   my $self = shift;
517 0           my($dest) = @_;
518 0           my $ct = ClearCase::Argv->new({autofail=>1, autochomp=>1});
519 0           my $pwd = getcwd;
520 0 0         $ct->_chdir($dest) || die "$0: Error: $dest: $!";
521 0           $dest = getcwd;
522 0           my @cs = grep /^\#\#:BranchOff: *root/, $ct->argv('catcs')->qx;
523 0 0         $ct->_chdir($pwd) || die "$0: Error: $pwd: $!";
524 0           return scalar @cs;
525             }
526              
527             sub analyze {
528 0     0 1   my $self = shift;
529 0 0         my $type = ref($_[0]) ? ${shift @_} : 'NORMAL';
  0            
530 0           my $sbase = $self->srcbase;
531 0           my $dbase = $self->dstbase;
532 0 0         die "$0: Error: must specify dest base before analyzing" if !$dbase;
533 0 0         die "$0: Error: must specify dest vob before analyzing" if !$self->dstvob;
534 0           $self->_mkbase;
535 0           $self->{branchoffroot} = $self->checkcs($dbase);
536             # Derive the add and modify lists by traversing the src map and
537             # comparing src/dst files.
538 0           delete $self->{ST_ADD};
539 0           delete $self->{ST_MOD};
540 0 0         my @sl = $dbase eq $self->{ST_MKBASE}? sort grep{-d $_}
  0            
541             $self->clone_ct->find($dbase, qw(-type l -print))->qx : ();
542 0           map { $_ = "/$_" } @sl if CYGWIN; # mismatch between conventions
543 0 0         if (@sl) {
544 0           my %sl = map{ $_ => 1} @sl;
  0            
545 0           for my $l (@sl) {
546 0           my $s = $l;
547 0           $s =~ s%^\Q$dbase\E/(.*)$%$1%;
548 0 0         if (exists $self->{ST_SRCMAP}->{$s}) {
549 0           $s = join('/', $sbase, $s);
550 0 0         delete $sl{$l} if src_slink($s);
551             }
552             }
553 0           @sl = sort keys %sl;
554             }
555 0 0         my $comparator = $self->no_cmp ? undef : $self->cmp_func;
556 0           SRC: for (sort keys %{$self->{ST_SRCMAP}}) {
  0            
557 0 0 0       next if $self->{ST_SRCMAP}->{$_}->{type} &&
558             $self->{ST_SRCMAP}->{$_}->{type} !~ /$type/;
559 0           my $src = join('/', $sbase, $_);
560 0 0 0       $src = $_ unless -e $src || src_slink($src);
561 0   0       my $dst = join('/', $dbase, $self->{ST_SRCMAP}->{$_}->{dst} || $_);
562 0           for my $s (@sl) {
563 0 0         if ($dst =~ /^\Q$s\E/) {
564 0           $self->{ST_DIRLNK}->{$s} = 1;
565 0           $self->{ST_ADD}->{$_}->{src} = $src;
566 0           $self->{ST_ADD}->{$_}->{dst} = $dst;
567 0           next SRC;
568             }
569             }
570             # It's possible for a symlink to not satisfy -e if it's dangling.
571             # Case-insensitive file test operators are a problem on Windows.
572             # You cannot modify files when they don't exist under the proper name.
573 0 0 0       if (! ecs($dst) && ! ccsymlink($dst)) {
    0 0        
574 0           $self->{ST_ADD}->{$_}->{src} = $src;
575 0           $self->{ST_ADD}->{$_}->{dst} = $dst;
576             } elsif (! -d $src || src_slink($src)) {
577 0 0         if ($self->_needs_update($src, $dst, $comparator)) {
578 0           $self->{ST_MOD}->{$_}->{src} = $src;
579 0           $self->{ST_MOD}->{$_}->{dst} = $dst;
580             }
581             }
582             }
583 0 0         if ($self->{ST_DIRLNK}) {
584 0           my @rem;
585 0           my @slst = sort keys %{$self->{ST_DIRLNK}};
  0            
586 0           for (reverse @slst) {
587 0           for my $l (@slst) {
588 0 0         if (/^\Q$l\E./) {
589 0           push @rem, $_;
590 0           last;
591             }
592             }
593             }
594 0 0         delete @{$self->{ST_DIRLNK}}{@rem} if @rem;
  0            
595 0 0         unlink $self->{ST_DIRLNK} unless keys %{$self->{ST_DIRLNK}};
  0            
596             }
597             # Last, check for subtractions but only if asked - it's potentially
598             # expensive and error-prone.
599 0 0         return unless $self->remove;
600 0           my(%dirs, %files, %xfiles);
601             my $wanted = sub {
602 0     0     my $path = $File::Find::name;
603 0 0         return if $path eq $dbase;
604 0 0         if ($path =~ /lost\+found/) {
605 0           $File::Find::prune = 1;
606 0           return;
607             }
608             # Get a relative path from the absolute path.
609 0           (my $relpath = $path) =~ s%^\Q$dbase\E\W?%%;
610 0 0         if (ccsymlink($path)) { # Vob link
    0          
    0          
611 0           $files{$relpath} = $path;
612             } elsif (-d $path) {
613 0           $dirs{$path} = $relpath;
614             } elsif (-f $path) {
615 0           $files{$relpath} = $path;
616             }
617 0           };
618 0           find($wanted, $dbase);
619 0           my %dst2src;
620 0           for (keys %{$self->{ST_SRCMAP}}) {
  0            
621 0           my $dst = $self->{ST_SRCMAP}->{$_}->{dst};
622 0 0         $dst2src{$dst} = $_ if $dst;
623             }
624 0           for (sort keys %files) {
625 0 0 0       next if $self->{ST_SRCMAP}->{$_} && !$self->{ST_SRCMAP}->{$_}->{dst};
626 0 0         $xfiles{$files{$_}}++ if !$dst2src{$_};
627             }
628 0           $self->{ST_SUB}->{exfiles} = \%xfiles;
629 0           $self->{ST_SUB}->{dirs} = \%dirs;
630             }
631              
632             sub preview {
633 0     0 0   my $self = shift;
634 0           my $indent = ' ' x 4;
635 0           my($adds, $mods, $subs) = (0, 0, 0);
636 0 0         if ($self->{ST_DIRLNK}) {
637 0           my $dl = keys %{$self->{ST_DIRLNK}};
  0            
638 0           print "Removing $dl directory symlinks:\n";
639 0           for (sort keys %{$self->{ST_DIRLNK}}) {
  0            
640 0           print "${indent}$_\n";
641             }
642             }
643 0 0         if ($self->{ST_ADD}) {
644 0           $adds = keys %{$self->{ST_ADD}};
  0            
645 0           print "Adding $adds elements:\n";
646 0           for (sort keys %{$self->{ST_ADD}}) {
  0            
647 0           printf "$indent%s +=>\n\t%s\n", $self->{ST_ADD}->{$_}->{src},
648             $self->{ST_ADD}->{$_}->{dst};
649             }
650             }
651 0 0         if ($self->{ST_MOD}) {
652 0           $mods = keys %{$self->{ST_MOD}};
  0            
653 0           print "Modifying $mods elements:\n";
654 0           for (sort keys %{$self->{ST_MOD}}) {
  0            
655 0           printf "$indent%s ==>\n\t%s\n", $self->{ST_MOD}->{$_}->{src},
656             $self->{ST_MOD}->{$_}->{dst};
657             }
658             }
659 0 0 0       if ($self->remove && $self->{ST_SUB}) {
660 0           my @exfiles = sort keys %{$self->{ST_SUB}->{exfiles}};
  0            
661 0           $subs = @exfiles;
662 0 0         print "Subtracting $subs elements:\n" if $subs;
663 0           for (@exfiles) {
664 0           printf "$indent%s\n", $_;
665             }
666             }
667 0           my $total = $adds + $mods + $subs;
668 0           print "Element change summary: add=$adds modify=$mods subtract=$subs\n";
669 0           return $total;
670             }
671              
672             sub pbrtype {
673 0     0 0   my $self = shift;
674 0           my $bt = shift;
675 0           my $ct = $self->clone_ct;
676 0           my $vob = $self->{ST_DSTVOB};
677 0 0         if (!defined($self->{ST_PBTYPES}->{$bt})) {
678 0           my $tc = $ct->des([qw(-fmt %[type_constraint]p)],
679             "brtype:$bt\@$vob")->qx;
680 0           $self->{ST_PBTYPES}->{$bt} = ($tc =~ /one version per branch/);
681             }
682 0           return $self->{ST_PBTYPES}->{$bt};
683             }
684              
685             sub branchco {
686 0     0 0   my $self = shift;
687 0           my $dir = shift;
688 0           my @ele = @_;
689 0           my $ct = $self->clone_ct({autochomp=>0});
690 0           my $rc;
691 0 0         if ($self->{branchoffroot}) {
692 0           foreach my $e (@ele) {
693 0           my $sel = $ct->ls(['-d'], $e)->autochomp(1)->qx;
694 0 0         if ($sel =~ /^(.*?) +Rule:.*-mkbranch (.*?)\]?$/) {
695 0           my ($ver, $bt) = ($1, $2);
696 0           my $sil = $self->clone_ct({stdout=>0, stderr=>0});
697 0           my $main = 'main';
698 0 0         if ($sil->des(['-s'], "$e\@\@/main/0")->system) {
699 0           $main = ($ct->lsvtree($e)->autochomp(1)->qx)[0];
700 0           $main =~ s%^[^@]*\@\@[\\/](.*)\r?$%$1%;
701             }
702 0 0         my $re = $self->pbrtype($bt) ?
703             qr([\\/]${main}[\\/]$bt[\\/]\d+$) : qr([\\/]$bt[\\/]\d+$);
704 0 0         if ($ver =~ m%$re%) {
705 0           $rc |= $ct->co($self->comment, $e)->system;
706             } else {
707 0           my $r = $ct->mkbranch([@{$self->comment}, '-ver',
  0            
708             "/${main}/0", $bt], $e)->system;
709 0 0         if ($r) {
710 0           $rc = 1;
711             } else {
712 0 0         if ($ver !~ m%\@\@[\\/]${main}[\\/]0$%) {
713 0 0         $rc |= $dir ?
714             $ct->merge(['-to', $e],
715             $ver)->stdout(0)->system :
716             $ct->merge(['-ndata', '-to', $e],
717             $ver)->stdout(0)->system;
718 0           unlink("$e.contrib");
719             }
720             }
721             }
722             } else {
723 0           $rc |= $ct->co($self->comment, $e)->system;
724             }
725             }
726             } else {
727 0           $rc = $ct->co($self->comment, @ele)->system;
728             }
729 0           return $rc;
730             }
731              
732             sub rmdirlinks {
733 0     0 0   my $self = shift;
734 0 0         return unless $self->{ST_DIRLNK};
735 0           my $lsco = ClearCase::Argv->lsco([qw(-s -d -cview)]);
736 0           for (sort {$b cmp $a} keys %{$self->{ST_DIRLNK}}) {
  0            
  0            
737 0           my $dad = dirname $_;
738 0 0         $self->branchco(1, $dad) unless $lsco->args($dad)->qx;
739 0           $self->clone_ct->rm($_)->system;
740 0           delete $self->{ST_SUB}->{exfiles}->{$_}; #If it is there
741             }
742             }
743              
744             sub mkrellink {
745 0     0 0   my ($self, $src) = @_;
746 0           my $txt = src_rlink($src);
747 0           my $sbase = $self->srcbase;
748 0 0 0       return $txt unless $self->{ST_RELLINKS} and ($txt =~ /^$sbase/);
749 0           $txt =~ s%^$sbase/(.*)%$1%;
750 0           $src =~ s%^$sbase/(.*)%$1%;
751 0           my @t = split m%/%, $txt;
752 0           my @s = split m%/%, $src;
753 0           my $i = 0;
754 0           while ($t[$i] eq $s[$i]) {
755 0           $i++;
756 0           shift @t;
757 0           shift @s;
758             }
759 0           while ($i++ < $#s) { unshift @t, '..'; }
  0            
760 0           $txt = join '/', @t;
761 0           return $txt;
762             }
763              
764             # Remove spurious names from restored directory
765             sub skimdir {
766 0     0 0   my ($self, $dst, $pfx) = @_;
767 0           my $flt = qr{^(\Q$pfx\E.*?)(?:/.*)?$}; # paths normalized
768 0           opendir(DIR, $dst);
769 0           my @f = grep !m%^\.\.?$%, readdir DIR;
770 0           closedir DIR;
771 0           my %ok = map {$_ => 1} grep s%$flt%$1%, keys %{$self->{ST_SRCMAP}};
  0            
  0            
772 0           for (@f) {
773 0           my $f = $pfx . $_;
774 0 0         $self->{ST_SUB}->{exfiles}->{join('/', $dst, $_)}++ unless $ok{$f};
775             }
776             }
777              
778             sub vtree {
779 0     0 0   my ($self, $dir) = @_;
780 0 0         if (!exists $self->{ST_VT}->{$dir}) {
781 0           my $vt = ClearCase::Argv->lsvtree({autochomp=>1}, [qw(-a -s -nco)]);
782             # optimization: branch/0 of a directory is either empty or duplicate
783 0 0         my @vt = reverse grep { m%[/\\](\d+)$% && $1>=1 } $vt->args($dir)->qx;
  0            
784 0           $self->{ST_VT}->{$dir} = \@vt;
785             }
786 0           return $self->{ST_VT}->{$dir};
787             }
788              
789             # Once a directory version was found, move it first in the list for next tries
790             sub raise_dver {
791 0     0 0   my ($self, $i, $dir) = @_;
792 0 0         return unless $i;
793 0           my $vt = $self->{ST_VT}->{$dir};
794 0           my $ver = splice @{$vt}, $i, 1;
  0            
795 0           unshift @{$vt}, $ver;
  0            
796             }
797              
798             # Reuse from removed elements, or create as view private, directories
799             sub reusemkdir {
800 0     0 0   my ($self, $dref, $rref) = @_;
801 0           my (%found, %dfound, %priv);
802 0           my $snapview = $self->snapdest;
803 0           my $ds = ClearCase::Argv->desc({stderr=>1},[qw(-s)]);
804 0           my $dm = ClearCase::Argv->desc([qw(-fmt %m)]);
805 0           my $rm = ClearCase::Argv->rm;
806 0           my $lsco = ClearCase::Argv->lsco([qw(-s -d -cview)]);
807 0           my $ln = ClearCase::Argv->ln;
808 0           for my $dst (sort keys %{$dref}) {
  0            
809 0 0         next if $dfound{$dst};
810 0           my $reused;
811 0           my($name, $dir) = fileparse($dst);
812 0 0         if (!$priv{$dir}) {
813 0 0         if ($rref->{$dst}) {
814 0 0         $self->branchco(1, $dir) unless $lsco->args($dir)->qx;
815 0           $rm->args($dst)->system;
816             }
817 0           my $i = -1; #index in the vtree list
818 0           VER: for (@{$self->vtree($dir)}) {
  0            
819 0           $i++;
820 0           my $dirext = "$_/$name";
821             # case-insensitive file test operator on Windows is a problem
822 0 0         if ($snapview ? $ds->args($dirext)->qx !~ /Error:/ : ecs($dirext)) {
    0          
823 0 0         next if $dm->args($dirext)->qx eq 'file element';
824 0           while (ccsymlink($dirext)) {
825 0           $name = readcclink $dirext;
826 0           $name =~ s/\@\@$//;
827 0           $dirext = "$_/$name";
828             # consider only relative, and local symlinks
829 0 0 0       next VER if !ecs($dirext) ||
830             $dm->args($dirext)->qx eq 'file element';
831             }
832 0           $reused = 1;
833 0           $self->raise_dver($i, $dir);
834 0 0         $self->branchco(1, $dir) unless $lsco->args($dir)->qx;
835 0           $ln->args($dirext, $dst)->system;
836             # Need to reevaluate all the files under this dir
837             # The case of implicit dirs, is recorded as '.'
838 0 0         my $d = $dref->{$dst} eq '.'? '' : $dref->{$dst} . '/';
839 0 0         $self->skimdir($dst, $d) if $self->remove;
840 0 0         my $cmp = $self->no_cmp ? undef : $self->cmp_func;
841 0           my @keys = sort $d? grep m%^\Q$d\E%, keys %{$self->{ST_ADD}}
  0            
842 0 0         : keys %{$self->{ST_ADD}};
843 0           for my $e (@keys) {
844 0           my $edst = join '/', $self->dstbase, $e;
845 0           my @intdir = split m%/%, $e;
846 0           pop @intdir;
847 0 0         if (@intdir) {
848 0           my $dd = $self->dstbase;
849 0           my $pf = '';
850 0           while (my $id = shift @intdir) {
851 0           $dd = join '/', $dd, $id;
852 0           $pf = $pf . $id . '/';
853 0 0 0       $self->skimdir($dd, $pf) if -d $dd && !$dfound{$dd}++;
854             }
855             }
856             # Problem: does it match the type under srcbase?
857 0 0 0       if (-d $edst and !ccsymlink($edst)) { # We know it is empty
858 0           opendir(DIR, $edst);
859 0           my @f = grep !m%^\.\.?$%, readdir DIR;
860 0           closedir DIR;
861 0 0         if (@f) {
862 0 0         $self->branchco(1, $edst)
863             unless $lsco->args($edst)->qx;
864 0           $rm->args(map{join '/', $edst, $_} @f)->system;
  0            
865             }
866 0           $dfound{$edst}++; #Skip in this loop
867 0           next;
868             }
869 0 0         if (exists($self->{ST_ADD}->{$e}->{dst})) {
870 0           my $src = $self->{ST_ADD}->{$e}->{src};
871 0           my $dst = $self->{ST_ADD}->{$e}->{dst};
872 0 0         if (-e $dst) {
873 0 0         $self->{ST_MOD}->{$e} = $self->{ST_ADD}->{$e}
874             if $self->_needs_update($src, $dst, $cmp);
875 0           $found{$e}++; #Remove from the add list
876             }
877             }
878             }
879 0           last;
880             }
881             }
882             }
883 0 0         if (!$reused) {
884 0           my $err;
885 0           mkpath($dst, {error => \$err, verbose => 0, mode => 0777});
886 0 0 0       $self->failm(join(': ', %{$err->[0]})) if $err and @{$err};
  0            
  0            
887 0           $priv{"${dst}/"}++;
888             }
889             }
890 0           return %found;
891             }
892              
893             # recursively record parent directories, and clashing objects to remove
894             sub recadd {
895 0     0 0   my ($self, $src, $dst, $dir, $rm, $seen) = @_;
896 0           my $dad = dirname($dst);
897 0 0 0       return if $seen->{$dad}++ || (-d $dad && !ccsymlink($dad)); #exists, normal
      0        
898 0           my $sdad = dirname($src);
899 0           $self->recadd($sdad, $dad, $dir, $rm, $seen);
900 0 0 0       $rm->{$dad}++ if -f $dad || ccsymlink($dad); #something clashing: remove
901 0           $dir->{$dad} = $sdad;
902             }
903              
904             sub add {
905 0     0 1   my $self = shift;
906 0           my $sbase = $self->srcbase;
907 0           my $mbase = $self->_mkbase;
908 0           my $ct = $self->clone_ct;
909 0 0         return if ! $self->{ST_ADD};
910 0 0         if ($self->reuse) { # First, reuse parent directories
911 0           my (%dir, %rm, %dseen);
912             # in the way if added in _mkbase as view private; ignore failures
913 0           rmdir($_) for reverse sort @{$self->{ST_IMPLICIT_DIRS}};
  0            
914 0           for my $d (sort keys %{$self->{ST_ADD}}) {
  0            
915 0           my $src = $self->{ST_ADD}->{$d}->{src};
916 0           my $dst = $self->{ST_ADD}->{$d}->{dst};
917 0 0 0       $dir{$dst} = $d if -d $src && !src_slink($src); # empty dir
918 0           $self->recadd($d, $dst, \%dir, \%rm, \%dseen);
919             }
920 0           my %found = $self->reusemkdir(\%dir, \%rm);
921 0           delete $self->{ST_ADD}->{$_} for keys %found;
922             }
923 0           for (sort keys %{$self->{ST_ADD}}) {
  0            
924 0           my $src = $self->{ST_ADD}->{$_}->{src};
925 0           my $dst = $self->{ST_ADD}->{$_}->{dst};
926 0           my $err;
927 0 0 0       if (-d $src && ! src_slink($src)) { # Already checked in the reuse case
    0          
    0          
928 0 0         -e $dst || mkpath($dst, {error=>\$err, verbose=>0, mode=>0777});
929 0 0 0       $self->failm(join(': ', %{$err->[0]})) if $err and @{$err};
  0            
  0            
930             } elsif (-e $src) {
931 0           my $dad = dirname($dst);
932 0 0         -d $dad || mkpath($dad, {error=>\$err, verbose=>0, mode=>0777});
933 0 0 0       $self->failm(join(': ', %{$err->[0]})) if $err and @{$err};
  0            
  0            
934 0 0         if (src_slink($src)) {
935 0 0         open(SLINK, ">$dst$lext") || $self->failm("$dst$lext: $!");
936 0           print SLINK $self->mkrellink($src), "\n";;
937 0           close(SLINK);
938             } else {
939 0 0         $self->{ST_CI_FROM}->{$_} = $self->{ST_ADD}->{$_}
940             if !exists($self->{ST_PRE}->{$dst});
941             }
942             } elsif (src_slink($src)) { #Dangling symlink: import
943 0 0         open(SLINK, ">$dst$lext") || $self->failm("$dst$lext: $!");
944 0           print SLINK $self->mkrellink($src), "\n";;
945 0           close(SLINK);
946             } else {
947 0           $ct->failm("$src: no such file or directory");
948             }
949             }
950 0           my @candidates = sort $self->_lsprivate(1),
951 0           map { $_->{dst} } values %{$self->{ST_CI_FROM}};
  0            
952 0 0         return if !@candidates;
953             # We'll be separating the elements-to-be into files and directories.
954 0           my(%files, @symlinks, %dirs);
955             # If the parent directories of any of the candidates are
956             # already versioned, we'll need to check them out unless
957             # it's already been done.
958 0           my @dads = sort map {dirname($_)} @candidates;
  0            
959 0           my %lsd = map {split(/\s+Rule:\s+/, $_, 2)}
  0            
960             $ct->argv('ls', [qw(-d -nxn -vis -vob)], @dads)->qx;
961 0           for my $dad (keys %lsd) {
962             # If already checked out, nothing to do.
963 0 0 0       next if ! $lsd{$dad} || $lsd{$dad} =~ /CHECKEDOUT$/;
964             # Now we know it's an element which needs to be checked out.
965 0           $dad =~ s%\\%/%g if MSWIN;
966 0           $dirs{$dad}++;
967             }
968 0 0         $self->branchco(1, keys %dirs) if keys %dirs;
969             # Process candidate directories here, then do files below.
970 0           my $mkdir = $self->clone_ct->mkdir({autofail=>0, autochomp=>0},
971             $self->comment);
972 0           for my $cand (@candidates) {
973 0 0         if (! -d $cand) {
974 0 0         if ($cand =~ /$lext$/) {
975 0           push(@symlinks, $cand);
976             } else {
977 0           $files{$cand} = 1;
978             }
979 0           next;
980             }
981             # Now we know we're dealing with directories. These cannot
982             # exist at mkelem time so we move them aside, make
983             # a versioned dir, then move all the files from the original
984             # back into the new dir (still as view-private files).
985 0           my $tmpdir = "$cand.$$.keep.d";
986 0 0         if (!rename($cand, $tmpdir)) {
987 0           warn "$0: Error: can't rename '$cand' to '$tmpdir': $!\n";
988 0           $ct->fail;
989 0           next;
990             }
991 0 0         if ($mkdir->args($cand)->system) {
992 0 0         warn "Warning: unable to rename $tmpdir back to $cand!"
993             unless rename($tmpdir, $cand);
994 0           $ct->fail;
995 0           next;
996             }
997 0 0         if (!opendir(DIR, $tmpdir)) {
998 0           warn "$0: Error: $tmpdir: $!";
999 0           $ct->fail;
1000 0           next;
1001             }
1002 0           while (defined(my $i = readdir(DIR))) {
1003 0 0 0       next if $i eq '.' || $i eq '..';
1004 0 0         rename("$tmpdir/$i", "$cand/$i") || $self->failm("$cand/$i: $!");
1005             }
1006 0           closedir DIR;
1007 0 0         rmdir $tmpdir || warn "$0: Error: $tmpdir: $!";
1008             }
1009              
1010             # Optionally, reconstitute an old element of the same name if present.
1011 0 0         if ($self->reuse) {
1012 0           my $snapview = $self->snapdest;
1013 0           my $ds = ClearCase::Argv->desc({stderr=>1}, [qw(-s)]);
1014 0           my $dm = ClearCase::Argv->desc([qw(-fmt %m)]);
1015 0           my $ln = ClearCase::Argv->ln;
1016 0           my %reused;
1017 0           for my $elem (keys %files) {
1018 0           my($name, $dir) = fileparse($elem);
1019 0           my $i = -1;
1020 0           DVER:
1021 0           for (@{$self->vtree($dir)}) {
1022 0           $i++;
1023 0           my $dirext = "$_/$name@@";
1024             # case-insensitive file test operator on Windows is a problem
1025 0 0         if ($snapview ? $ds->args($dirext)->qx !~ /Error:/ :
    0          
1026             ecs("$_/$name")) {
1027 0 0         next if $dm->args("$_/$name")->qx =~ /^directory /;
1028 0           while (ccsymlink("$_/$name")) {
1029 0           $name = readcclink "$_/$name";
1030 0           $name =~ s/\@\@$//;
1031 0 0 0       next DVER if !ecs("$_/$name") ||
1032             $dm->args("$_/$name")->qx =~ /^directory /;
1033             }
1034 0           $reused{$elem} = 1;
1035 0           delete $files{$elem};
1036 0           unlink($elem);
1037 0           $ln->args("$_/$name", $elem)->system;
1038 0           $self->raise_dver($i, $dir);
1039 0           last;
1040             }
1041             }
1042             }
1043             # If any elements were "reconstituted", they must be taken off the
1044             # list of elems to be checked in explicitly, since 'ct ln' is
1045             # just a directory op.
1046 0           my %xkeys;
1047 0 0 0       if (!$self->no_cr && %reused) {
1048 0           for (keys %{$self->{ST_CI_FROM}}) {
  0            
1049 0 0 0       if (exists($self->{ST_CI_FROM}->{$_})
      0        
1050             && exists($self->{ST_CI_FROM}->{$_}->{dst})
1051             && exists($reused{$self->{ST_CI_FROM}->{$_}->{dst}})) {
1052 0           $xkeys{$_} = 1;
1053             }
1054             }
1055 0           for (keys %xkeys) {
1056 0           delete $self->{ST_CI_FROM}->{$_};
1057             }
1058             }
1059             # Also, reconstituted elements may now be candidates for
1060             # modification. Re-analyze the status for these. If any of
1061             # them differ from their counterparts in the src area, copy
1062             # them from the ADD list to the MOD list.
1063 0 0         my $comparator = $self->no_cmp ? undef : $self->cmp_func;
1064 0           for my $elem (keys %{$self->{ST_ADD}}) {
  0            
1065 0 0         if (exists($reused{$self->{ST_ADD}->{$elem}->{dst}})) {
1066 0           my $src = $self->{ST_ADD}->{$elem}->{src};
1067 0           my $dst = $self->{ST_ADD}->{$elem}->{dst};
1068 0 0         if ($self->_needs_update($src, $dst, $comparator)) {
1069 0           $self->{ST_MOD}->{$elem} = $self->{ST_ADD}->{$elem};
1070             }
1071             }
1072             }
1073             }
1074 0           for (sort keys %{$self->{ST_CI_FROM}}) {
  0            
1075 0           my $dst = $self->{ST_CI_FROM}->{$_}->{dst};
1076 0 0         if ($files{$dst}) {
1077 0           my $src = $self->{ST_CI_FROM}->{$_}->{src};
1078 0 0         copy($src, $dst) || $ct->failm("$_: $!");
1079 0 0         utime(time(), (stat $src)[9], $dst) ||
1080             warn "Warning: $dst: touch failed";
1081             }
1082             }
1083             # Now do the files in one fell swoop.
1084 0 0         $ct->mkelem($self->comment, sort keys %files)->system if %files;
1085              
1086             # Deal with symlinks.
1087 0           for my $symlink (@symlinks) {
1088 0           (my $lnk = $symlink) =~ s/$lext$//;
1089 0 0         if (!open(SLINK, $symlink)) {
1090 0           warn "$symlink: $!";
1091 0           next;
1092             }
1093 0           chomp(my $txt = );
1094 0           close SLINK;
1095 0           unlink $symlink;
1096 0           $ct->ln(['-s'], $txt, $lnk)->system;
1097             }
1098             }
1099              
1100             # Tried to use Cwd::abs_path, but it behaves differently on Cygwin and UNIX
1101             sub absdst {
1102 0     0 0   my ($self, $dir, $f) = @_;
1103 0 0         if ($f =~ /^\./) {
1104 0           my $sep = qr{[/\\]};
1105 0           my @d = split $sep, $dir;
1106 0           while ($f =~ s/^(\.\.?$sep)//) {
1107 0 0         pop @d if $1 =~ /^\.{2}/;
1108             }
1109 0           $dir = join '/', @d;
1110             }
1111 0           return File::Spec->catfile($dir, $f);
1112             }
1113              
1114             sub modify {
1115 0     0 1   my $self = shift;
1116 0 0         return if !keys %{$self->{ST_MOD}};
  0            
1117 0           my(%files, %symlinks);
1118 0           for (keys %{$self->{ST_MOD}}) {
  0            
1119 0 0         if (src_slink($self->{ST_MOD}->{$_}->{src})) {
1120 0           $symlinks{$_}++;
1121             } else {
1122 0           $files{$_}++;
1123             }
1124             }
1125 0           my $rm = $self->clone_ct('rmname');
1126 0           my $ln = $self->clone_ct('ln');
1127 0           $ln->opts('-s', $ln->opts);
1128 0           my $lsco = ClearCase::Argv->lsco([qw(-s -d -cview)]);
1129 0 0         my $comparator = $self->no_cmp ? undef : $self->cmp_func;
1130 0 0         if (keys %files) {
1131 0           my (@toco, @del);
1132 0           for my $key (sort keys %files) {
1133 0           my $src = $self->{ST_MOD}->{$key}->{src};
1134 0           my $dst = $self->{ST_MOD}->{$key}->{dst};
1135 0           my $new;
1136 0 0         if (ccsymlink($dst)) {
1137             # The source is a file, but the destination is a symlink: look
1138             # (recursively) at what this one points to, and link in this
1139             # file.
1140             # Build up the path of the destination, in such a way that it
1141             # may be found, or not, in the hash.
1142 0           my $dangling;
1143 0           my $sep = qr%[/\\]%;
1144 0           my $dst1 = $dst;
1145 0           while (ccsymlink($dst1)) {
1146 0           my $tgt = readcclink $dst1;
1147 0           my $dir = dirname $dst1;
1148 0 0         $tgt = $self->absdst($dir, $tgt) unless $tgt =~ m%^[/\\]%;
1149 0           $tgt =~ s%\\%/%g if MSWIN;
1150 0 0         if (-e $tgt) {
1151 0           $dst1 = $tgt;
1152             } else {
1153 0           $dangling = 1;
1154 0           last;
1155             }
1156             }
1157 0           my $dir = dirname($dst);
1158 0 0         $self->branchco(1, $dir) unless $lsco->args($dir)->qx;
1159 0           $self->clone_ct->rm($dst)->system; #remove the first symlink
1160 0 0 0       if ($dangling || !$self->{ST_SUB}->{exfiles}->{$dst1}) {
1161 0 0         if (!copy($src, $dst)) {
1162 0           warn "$0: Error: $dst: $!\n";
1163 0           $rm->fail;
1164             }
1165 0 0         utime(time(), (stat $src)[9], $dst) ||
1166             warn "Warning: $dst: touch failed";
1167 0           $self->clone_ct->mkelem($self->comment, $dst)->system;
1168 0           $new = 1;
1169 0           delete $self->{ST_MOD}->{$key};
1170 0           push @del, $key;
1171             } else {
1172 0           my $dir1 = dirname($dst1);
1173 0 0 0       $self->branchco(1, $dir1)
1174             unless ($dir eq $dir1) || $lsco->args($dir1)->qx;
1175 0           $self->clone_ct->mv($dst1, $dst)->system;
1176 0           delete $self->{ST_SUB}->{exfiles}->{$dst1}; #done already
1177 0 0         if (!$self->_needs_update($src, $dst, $comparator)) {
1178 0           delete $self->{ST_MOD}->{$key};
1179 0           push @del, $key;
1180             }
1181             }
1182             }
1183 0 0 0       push(@toco, $dst) unless exists($self->{ST_PRE}->{$dst}) || $new;
1184             }
1185 0 0         $self->branchco(0, @toco) if @toco;
1186 0           delete @files{@del};
1187 0           for (sort keys %files) {
1188 0           my $src = $self->{ST_MOD}->{$_}->{src};
1189 0           my $dst = $self->{ST_MOD}->{$_}->{dst};
1190 0 0         next if exists($self->{ST_PRE}->{$dst});
1191 0 0         if ($self->no_cr) {
1192 0 0         if (!copy($src, $dst)) {
1193 0           warn "$0: Error: $dst: $!\n";
1194 0           $rm->fail;
1195 0           next;
1196             }
1197 0 0         utime(time(), (stat $src)[9], $dst) ||
1198             warn "Warning: $dst: touch failed";
1199             } else {
1200 0           $self->{ST_CI_FROM}->{$_} = $self->{ST_MOD}->{$_};
1201             }
1202             }
1203             }
1204 0 0         if (keys %symlinks) {
1205 0           my %checkedout = map {$_ => 1} $self->_lsco;
  0            
1206 0           for (sort keys %symlinks) {
1207 0           my $txt = $self->mkrellink($self->{ST_MOD}->{$_}->{src});
1208 0           my $lnk = $self->{ST_MOD}->{$_}->{dst};
1209 0           my $dad = dirname($lnk);
1210 0 0         if (!$checkedout{$dad}) {
1211 0 0         $checkedout{$dad} = 1 if ! $self->branchco(1, $dad);
1212             }
1213 0 0         if (!$rm->args($lnk)->system) {
1214 0           my @fil = grep /^\Q$lnk\E/, keys %{$self->{ST_SUB}->{exfiles}};
  0            
1215 0           delete @{$self->{ST_SUB}->{exfiles}}{@fil};
  0            
1216 0           delete $self->{ST_SUB}->{dirs}{$lnk};
1217             }
1218 0           $ln->args($txt, $lnk)->system;
1219             }
1220             }
1221             }
1222              
1223             sub subtract {
1224 0     0 1   my $self = shift;
1225 0 0         return unless $self->{ST_SUB};
1226 0           my $ct = $self->clone_ct;
1227 0           my %co = map {$_ => 1} $self->_lsco;
  0            
1228 0           my $exnames = $self->{ST_SUB}->{exfiles}; # Entries to remove
1229 0           my (%dir, %keep); # Directories respectively to inspect, and to keep
1230 0           $dir{dirname($_)}++ for keys %{$exnames};
  0            
1231 0           $dir{$_}++ for keys %{$self->{ST_SUB}->{dirs}}; # Existed originally
  0            
1232 0           my $dbase = $self->dstbase;
1233 0           for my $d (sort {$b cmp $a} keys %dir) {
  0            
1234 0 0         next if $keep{$d};
1235 0           my ($k) = ($d =~ m%^\Q$dbase\E/(.*)$%);
1236 0 0 0       if ($k and $self->{ST_SRCMAP}->{$k}) {
1237 0           delete $exnames->{$d};
1238 0           my $dad = $d;
1239 0   0       $keep{$dad}++ while $dad = dirname($dad) and $dad gt $dbase;
1240 0           next;
1241             }
1242 0 0         if (opendir(DIR, $d)) {
1243 0           my @entries = grep !/^\.\.?$/, readdir DIR;
1244 0           closedir(DIR);
1245 0           map { $_ = join('/', $d, $_) } @entries;
  0            
1246 0 0 0       if (grep { !$exnames->{$_} and $ct->ls(['-s'], $_)->qx !~ /\@$/}
  0            
1247             @entries) { # Something not to delete--some version selected
1248 0           my $dad = $d;
1249 0   0       $keep{$dad}++ while $dad = dirname($dad) and $dad gt $dbase;
1250             } else {
1251 0 0         if (@entries) {
1252 0           my @co = grep {$co{$_}} @entries; # Checkin before removing
  0            
1253 0 0         $ct->ci($self->comment, @co)->system if @co;
1254 0           delete @$exnames{@entries}; # Remove the contents
1255             }
1256 0           $exnames->{$d}++; # Add the container
1257             }
1258             }
1259             }
1260 0           delete @$exnames{keys %keep};
1261 0           my @exnames = keys %{$exnames};
  0            
1262 0           for my $dad (map {dirname($_)} @exnames) {
  0            
1263 0 0         $self->branchco(1, $dad) unless $co{$dad}++;
1264             }
1265             # Force because of possible checkouts in other views. Fail for unreachable
1266 0 0         $ct->rm([@{$self->comment}, '-f'], @exnames)->system if @exnames;
  0            
1267             }
1268              
1269             sub label {
1270 0     0 1   my $self = shift;
1271 0   0       my $lbtype = shift || $self->lbtype;
1272 0 0         return unless $lbtype;
1273 0           my $dbase = $self->dstbase;
1274 0           my $ct = $self->clone_ct({autochomp=>0});
1275 0           my $ctq = $self->clone_ct({stdout=>0});
1276 0           my $ctbool = $self->clone_ct({autofail=>0, stdout=>0, stderr=>0});
1277 0           my $dvob = $self->dstvob;
1278 0           my $locked;
1279 0 0         if ($ctbool->lstype(['-s'], "lbtype:$lbtype\@$dvob")->system) {
    0          
1280 0           $ct->mklbtype($self->comment, "lbtype:$lbtype\@$dvob")->system;
1281             } elsif (!$self->inclb) {
1282 0           $locked = $ct->lslock(['-s'], "lbtype:$lbtype\@$dvob")->qx;
1283 0 0         $ct->unlock("lbtype:$lbtype\@$dvob")->system if $locked;
1284             }
1285             # Allow for labelling errors, in case of hard links: only the link
1286             # recorded can be labelled, the other being seen as 'removed'
1287 0 0 0       if ($self->label_mods || $self->inclb) {
1288 0           my @mods = $self->_lsco;
1289 0 0         push @mods, @{$self->{ST_LBL}} if $self->{ST_LBL};
  0            
1290 0 0         if (@mods) {
1291 0 0         $ctbool->mklabel([qw(-nc -rep), $self->inclb], @mods)->system
1292             if $self->inclb;
1293 0           $ctbool->mklabel([qw(-nc -rep), $lbtype], @mods)->system;
1294             }
1295             } else {
1296 0           my $lbl = $self->lblver;
1297 0 0         if ($lbl) {
1298 0           my $ct = $self->clone_ct({autochomp=>1, autofail=>0, stderr=>0});
1299 0 0 0       my @rv = grep{ s/^(.*?)(?:@@(.*))/$1/ &&
  0            
1300             ($2 =~ /CHECKEDOUT$/ || !-r "$_\@\@/$lbl") }
1301             $ct->ls([qw(-r -vob -s)], $dbase)->qx,
1302             $ct->ls([qw(-d -vob -s)], $dbase)->qx;
1303 0           $ctbool->mklabel([qw(-nc -rep), $lbtype], $dbase, @rv)->system;
1304             } else {
1305 0           $ctbool->mklabel([qw(-nc -rep -rec), $lbtype], $dbase)->system;
1306             }
1307             # Possibly move the label back to the right versions
1308 0 0         $ctbool->mklabel([qw(-nc -rep), $lbtype], @{$self->{ST_LBL}})->system
  0            
1309             if $self->{ST_LBL};
1310             # Last, label the ancestors of the destination back to the vob tag.
1311 0           my($dad, @ancestors);
1312 0           my $min = length($self->normalize($dvob));
1313 0           for ($dad = dirname($dbase);
1314             length($dad) >= $min; $dad = dirname($dad)) {
1315 0           push(@ancestors, $dad);
1316             }
1317 0 0         $ctq->mklabel([qw(-rep -nc), $lbtype], @ancestors)->system
1318             if @ancestors;
1319             }
1320 0 0         $self->clone_ct->lock("lbtype:$lbtype\@$dbase")->system if $locked;
1321             }
1322              
1323             sub get_addhash {
1324 0     0 0   my $self = shift;
1325 0 0         if ($self->{ST_ADD}) {
1326             return
1327 0           map { $self->{ST_ADD}->{$_}->{src}, $self->{ST_ADD}->{$_}->{dst} }
  0            
1328 0           keys %{$self->{ST_ADD}};
1329             } else {
1330 0           return ();
1331             }
1332             }
1333              
1334             sub get_modhash {
1335 0     0 0   my $self = shift;
1336 0 0         if ($self->{ST_MOD}) {
1337             return
1338 0           map { $self->{ST_MOD}->{$_}->{src}, $self->{ST_MOD}->{$_}->{dst} }
  0            
1339 0           keys %{$self->{ST_MOD}};
1340             } else {
1341 0           return ();
1342             }
1343             }
1344              
1345             sub get_sublist {
1346 0     0 0   my $self = shift;
1347 0 0         if ($self->{ST_SUB}) {
1348 0           return sort keys %{$self->{ST_SUB}->{exfiles}};
  0            
1349             } else {
1350 0           return ();
1351             }
1352             }
1353              
1354             sub checkin {
1355 0     0 1   my $self = shift;
1356 0           my $mbase = $self->_mkbase;
1357 0           my $dad = dirname($mbase);
1358 0 0         my @ptime = qw(-pti) unless $self->ctime;
1359 0           my @cmnt = @{$self->comment};
  0            
1360 0           my $ct = $self->clone_ct({autochomp=>0});
1361             # If special eltypes are registered, chtype them here.
1362 0 0         if (my %emap = $self->eltypemap) {
1363 0           for my $re (keys %emap) {
1364 0           my @chtypes = grep {/$re/} map {$self->{ST_ADD}->{$_}->{dst}}
  0            
  0            
1365 0           keys %{$self->{ST_ADD}};
1366 0 0         next unless @chtypes;
1367 0           $ct->chtype([@cmnt, '-f', $emap{$re}], @chtypes)->system;
1368             }
1369             }
1370             # Do one-by-one ci's with -from (to preserve CR's) unless
1371             # otherwise requested.
1372 0 0         if (! $self->no_cr) {
1373 0           for (keys %{$self->{ST_CI_FROM}}) {
  0            
1374 0           my $src = $self->{ST_CI_FROM}->{$_}->{src};
1375 0           my $dst = $self->{ST_CI_FROM}->{$_}->{dst};
1376 0           $ct->ci([@ptime, @cmnt, qw(-ide -rm -from), $src], $dst)->system;
1377             }
1378 0           delete @{$self->{ST_MOD}}{keys %{$self->{ST_CI_FROM}}};
  0            
  0            
1379             }
1380             # Check-in first the files modified under the recorded names,
1381             # in case of hardlinks, since checking the other link first
1382             # in a pair would fail.
1383 0           my @mods;
1384 0           push @mods, $self->{ST_MOD}->{$_}->{dst} for
  0            
1385 0           grep {!ccsymlink($self->{ST_MOD}->{$_}->{dst})} keys %{$self->{ST_MOD}};
1386 0 0         $ct->ci([@cmnt, '-ide', @ptime], sort @mods)->system if @mods;
1387             # Check in anything not handled above.
1388 0           my %checkedout = map {$_ => 1} $self->_lsco;
  0            
1389 0           my @todo = grep {m%^\Q$mbase%} keys %checkedout;
  0            
1390 0 0         @todo = grep {!exists($self->{ST_PRE}->{$_})} @todo if $self->ignore_co;
  0            
1391 0 0         unshift(@todo, $dad) if $checkedout{$dad};
1392             # Sort reverse in case the checked in versions are not selected by the view
1393 0 0         $ct->argv('ci', [@cmnt, '-ide', @ptime], sort {$b cmp $a} @todo)->system
  0            
1394             if @todo;
1395             # Fix the protections of the target files if requested. Unix files
1396             # get careful consideration of bitmasks etc; Windows files just get
1397             # promoted to a+x if their extension looks executable.
1398 0 0         if ($self->protect) {
1399 0           if (MSWIN) {
1400             my @exes;
1401             for (keys %{$self->{ST_ADD}}) {
1402             next unless m%\.(bat|cmd|exe|dll|com|cgi|.?sh|pl)$%i;
1403             push(@exes, $self->{ST_ADD}->{$_}->{dst});
1404             }
1405             $ct->argv('protect', [qw(-chmod a+x)], @exes)->system if @exes;
1406             } else {
1407 0           my %perms;
1408 0           for (keys %{$self->{ST_ADD}}) {
  0            
1409 0           my $src = $self->{ST_ADD}->{$_}->{src};
1410 0           my $dst = $self->{ST_ADD}->{$_}->{dst};
1411 0           my $src_mode = (stat $src)[2];
1412 0           my $dst_mode = (stat $dst)[2];
1413             # 07551 represents the only bits that matter to clearcase
1414 0 0 0       if (($src_mode & 07551) ne ($dst_mode & 07551) &&
1415             $src !~ m%\.(?:p|html?|gif|mak|rc|ini|java|
1416             c|cpp|cxx|h|bmp|ico)$|akefile%x) {
1417 0           my $sym = sprintf("%o", ($src_mode & 07775) | 0444);
1418 0           push(@${$perms{$sym}}, $dst);
  0            
1419             }
1420             }
1421 0           for (keys %{$self->{ST_MOD}}) {
  0            
1422 0           my $src = $self->{ST_MOD}->{$_}->{src};
1423 0           my $dst = $self->{ST_MOD}->{$_}->{dst};
1424 0           my $src_mode = (stat $src)[2];
1425 0           my $dst_mode = (stat $dst)[2];
1426             # 07551 represents the only bits that matter to clearcase
1427 0 0 0       if (($src_mode & 07551) ne ($dst_mode & 07551) &&
1428             $src !~ m%\.(?:p|html?|gif|mak|rc|ini|java|
1429             c|cpp|cxx|h|bmp|ico)$|akefile%x) {
1430 0           my $sym = sprintf("%o", ($src_mode & 07775) | 0444);
1431 0           push(@${$perms{$sym}}, $dst);
  0            
1432             }
1433             }
1434 0           for (keys %perms) {
1435 0           $ct->argv('protect', ['-chmod', $_], @${$perms{$_}})->system;
  0            
1436             }
1437             }
1438             }
1439             }
1440              
1441             sub cleanup {
1442 0     0 1   my $self = shift;
1443 0           my $mbase = $self->_mkbase;
1444 0           my $dad = dirname($mbase);
1445 0           my $ct = $self->clone_ct({autofail=>0});
1446 0           my @vp = $self->_lsprivate(1);
1447 0           for (sort {$b cmp $a} @vp) {
  0            
1448 0 0         if (-d $_) {
1449 0 0         rmdir $_ || warn "$0: Error: unable to remove $_\n";
1450             } else {
1451 0   0       unlink $_ || warn "$0: Error: unable to remove $_\n";
1452             }
1453             }
1454 0           my %checkedout = map {$_ => 1} $self->_lsco;
  0            
1455 0           my @todo = grep {m%^\Q$mbase%} keys %checkedout;
  0            
1456 0 0 0       @todo = grep {!exists($self->{ST_PRE}->{$_})} @todo
  0            
1457             if $self->ignore_co || $self->overwrite_co;
1458 0 0         unshift(@todo, $dad) if $checkedout{$dad};
1459 0 0         if ($self->{branchoffroot}) {
1460 0           for (sort {$b cmp $a} @todo) {
  0            
1461 0           my $b = $ct->ls([qw(-s -d)], $_)->qx;
1462 0           $ct->unco([qw(-rm)], $_)->system;
1463 0 0         if ($b =~ s%^(.*)[\\/]CHECKEDOUT$%$1%) {
1464 0 0         opendir BR, $b or next;
1465 0           my @f = grep !/^(\.\.?|0|LATEST)$/, readdir BR;
1466 0           closedir BR;
1467 0 0         $ct->rmbranch([qw(-f)], $b)->system unless @f;
1468             }
1469             }
1470             } else {
1471 0 0         $ct->unco([qw(-rm)], sort {$b cmp $a} @todo)->system if @todo;
  0            
1472             }
1473             }
1474              
1475             # Undo current work and exit. May be called from an exception handler.
1476             sub fail {
1477 0     0 1   my $self = shift;
1478 0           my $rc = shift;
1479 0           $self->ct->autofail(0); # avoid exception-handler loop
1480 0           $self->cleanup;
1481 0 0         exit(defined($rc) ? $rc : 2);
1482             }
1483              
1484             sub failm {
1485 0     0 0   my ($self, $msg, $rc) = @_;
1486 0           warn "$0: Error: $msg\n";
1487 0           $self->fail($rc);
1488             }
1489              
1490             sub version {
1491 0     0 0   my $self = shift;
1492 0           return $ClearCase::SyncTree::VERSION;
1493             }
1494              
1495             # Here 'ecs' means Exists Case Sensitive. We don't generally
1496             # want the case-insensitive file test operators on Windows.
1497             # The underlying problem is that cleartool is always case
1498             # sensitive. I.e. you can mkelem 'Foo' and then open 'foo'
1499             # if you have the right MVFS settings, but you cannot check
1500             # out or describe 'foo', only 'Foo'.
1501             # This could lead to other problems on Windows though, since you
1502             # may create evil twins if you subtract an old name and
1503             # then add it under a name which differs only by case. But at
1504             # least that does work, whereas trying to checkout a path
1505             # with the wrong case does not work at all. Let the evil twin
1506             # trigger handle the evil twin scenario.
1507             sub ecs {
1508 0     0 0   my $file = shift;
1509 0           my $rc = 0;
1510 0           if (MSWIN || CYGWIN) {
1511             if (opendir DIR, dirname($file)) {
1512             my $match = basename($file);
1513             # Faster than for/last when not found!
1514             $rc = 1 if grep {$_ eq $match} readdir DIR;
1515             closedir DIR;
1516             }
1517             } else {
1518 0           $rc = -e $file;
1519             }
1520 0           return $rc;
1521             }
1522              
1523             1;
1524              
1525             __END__