File Coverage

blib/lib/Stow.pm
Criterion Covered Total %
statement 634 720 88.0
branch 270 370 72.9
condition 36 48 75.0
subroutine 58 60 96.6
pod 12 50 24.0
total 1010 1248 80.9


line stmt bran cond sub pod time code
1             #!/usr/bin/perl
2             #
3             # This file is part of GNU Stow.
4             #
5             # GNU Stow is free software: you can redistribute it and/or modify it
6             # under the terms of the GNU General Public License as published by
7             # the Free Software Foundation, either version 3 of the License, or
8             # (at your option) any later version.
9             #
10             # GNU Stow is distributed in the hope that it will be useful, but
11             # WITHOUT ANY WARRANTY; without even the implied warranty of
12             # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13             # General Public License for more details.
14             #
15             # You should have received a copy of the GNU General Public License
16             # along with this program. If not, see https://www.gnu.org/licenses/.
17              
18             package Stow;
19              
20             =head1 NAME
21              
22             Stow - manage farms of symbolic links
23              
24             =head1 SYNOPSIS
25              
26             my $stow = new Stow(%$options);
27              
28             $stow->plan_unstow(@pkgs_to_unstow);
29             $stow->plan_stow (@pkgs_to_stow);
30              
31             my %conflicts = $stow->get_conflicts;
32             $stow->process_tasks() unless %conflicts;
33              
34             =head1 DESCRIPTION
35              
36             This is the backend Perl module for GNU Stow, a program for managing
37             the installation of software packages, keeping them separate
38             (C vs. C, for example)
39             while making them appear to be installed in the same place
40             (C).
41              
42             Stow doesn't store an extra state between runs, so there's no danger
43             of mangling directories when file hierarchies don't match the
44             database. Also, stow will never delete any files, directories, or
45             links that appear in a stow directory, so it is always possible to
46             rebuild the target tree.
47              
48             =cut
49              
50 14     14   1143434 use strict;
  14         142  
  14         407  
51 14     14   77 use warnings;
  14         24  
  14         494  
52              
53 14     14   78 use Carp qw(carp cluck croak confess longmess);
  14         28  
  14         982  
54 14     14   7344 use File::Copy qw(move);
  14         32471  
  14         851  
55 14     14   99 use File::Spec;
  14         27  
  14         314  
56 14     14   6583 use POSIX qw(getcwd);
  14         82513  
  14         82  
57              
58 14         128453 use Stow::Util qw(set_debug_level debug error set_test_mode
59 14     14   24360 join_paths restore_cwd canon_path parent adjust_dotfile);
  14         34  
60              
61             our $ProgramName = 'stow';
62             our $VERSION = '2.3.1';
63              
64             our $LOCAL_IGNORE_FILE = '.stow-local-ignore';
65             our $GLOBAL_IGNORE_FILE = '.stow-global-ignore';
66              
67             our @default_global_ignore_regexps =
68             __PACKAGE__->get_default_global_ignore_regexps();
69              
70             # These are the default options for each Stow instance.
71             our %DEFAULT_OPTIONS = (
72             conflicts => 0,
73             simulate => 0,
74             verbose => 0,
75             paranoid => 0,
76             compat => 0,
77             test_mode => 0,
78             dotfiles => 0,
79             adopt => 0,
80             'no-folding' => 0,
81             ignore => [],
82             override => [],
83             defer => [],
84             );
85              
86             =head1 CONSTRUCTORS
87              
88             =head2 new(%options)
89              
90             =head3 Required options
91              
92             =over 4
93              
94             =item * dir - the stow directory
95              
96             =item * target - the target directory
97              
98             =back
99              
100             =head3 Non-mandatory options
101              
102             See the documentation for the F CLI front-end for information on these.
103              
104             =over 4
105              
106             =item * conflicts
107              
108             =item * simulate
109              
110             =item * verbose
111              
112             =item * paranoid
113              
114             =item * compat
115              
116             =item * test_mode
117              
118             =item * adopt
119              
120             =item * no-folding
121              
122             =item * ignore
123              
124             =item * override
125              
126             =item * defer
127              
128             =back
129              
130             N.B. This sets the current working directory to the target directory.
131              
132             =cut
133              
134             sub new {
135 78     78 1 144663 my $self = shift;
136 78   33     403 my $class = ref($self) || $self;
137 78         238 my %opts = @_;
138              
139 78         180 my $new = bless { }, $class;
140              
141 78         215 $new->{action_count} = 0;
142              
143 78         172 for my $required_arg (qw(dir target)) {
144             croak "$class->new() called without '$required_arg' parameter\n"
145 156 50       354 unless exists $opts{$required_arg};
146 156         421 $new->{$required_arg} = delete $opts{$required_arg};
147             }
148              
149 78         364 for my $opt (keys %DEFAULT_OPTIONS) {
150             $new->{$opt} = exists $opts{$opt} ? delete $opts{$opt}
151 936 100       1770 : $DEFAULT_OPTIONS{$opt};
152             }
153              
154 78 50       225 if (%opts) {
155 0         0 croak "$class->new() called with unrecognised parameter(s): ",
156             join(", ", keys %opts), "\n";
157             }
158              
159 78         214 set_debug_level($new->get_verbosity());
160 78         255 set_test_mode($new->{test_mode});
161 78         199 $new->set_stow_dir();
162 78         541 $new->init_state();
163              
164 78         656 return $new;
165             }
166              
167             sub get_verbosity {
168 78     78 0 133 my $self = shift;
169              
170 78 50       197 return $self->{verbose} unless $self->{test_mode};
171              
172 78 50       405 return 0 unless exists $ENV{TEST_VERBOSE};
173 0 0       0 return 0 unless length $ENV{TEST_VERBOSE};
174              
175             # Convert TEST_VERBOSE=y into numeric value
176 0 0       0 $ENV{TEST_VERBOSE} = 3 if $ENV{TEST_VERBOSE} !~ /^\d+$/;
177              
178 0         0 return $ENV{TEST_VERBOSE};
179             }
180              
181             =head2 set_stow_dir([$dir])
182              
183             Sets a new stow directory. This allows the use of multiple stow
184             directories within one Stow instance, e.g.
185              
186             $stow->plan_stow('foo');
187             $stow->set_stow_dir('/different/stow/dir');
188             $stow->plan_stow('bar');
189             $stow->process_tasks;
190              
191             If C<$dir> is omitted, uses the value of the C parameter passed
192             to the L constructor.
193              
194             =cut
195              
196             sub set_stow_dir {
197 81     81 1 3908 my $self = shift;
198 81         140 my ($dir) = @_;
199 81 100       174 if (defined $dir) {
200 3         6 $self->{dir} = $dir;
201             }
202              
203 81         218 my $stow_dir = canon_path($self->{dir});
204 81         238 my $target = canon_path($self->{target});
205 81         6040 $self->{stow_path} = File::Spec->abs2rel($stow_dir, $target);
206              
207 81         431 debug(2, "stow dir is $stow_dir");
208 81         264 debug(2, "stow dir path relative to target $target is $self->{stow_path}");
209             }
210              
211             sub init_state {
212 78     78 0 141 my $self = shift;
213              
214             # Store conflicts during pre-processing
215 78         144 $self->{conflicts} = {};
216 78         188 $self->{conflict_count} = 0;
217              
218             # Store command line packages to stow (-S and -R)
219 78         142 $self->{pkgs_to_stow} = [];
220              
221             # Store command line packages to unstow (-D and -R)
222 78         163 $self->{pkgs_to_delete} = [];
223              
224             # The following structures are used by the abstractions that allow us to
225             # defer operating on the filesystem until after all potential conflicts have
226             # been assessed.
227              
228             # $self->{tasks}: list of operations to be performed (in order)
229             # each element is a hash ref of the form
230             # {
231             # action => ... ('create' or 'remove' or 'move')
232             # type => ... ('link' or 'dir' or 'file')
233             # path => ... (unique)
234             # source => ... (only for links)
235             # dest => ... (only for moving files)
236             # }
237 78         144 $self->{tasks} = [];
238              
239             # $self->{dir_task_for}: map a path to the corresponding directory task reference
240             # This structure allows us to quickly determine if a path has an existing
241             # directory task associated with it.
242 78         135 $self->{dir_task_for} = {};
243              
244             # $self->{link_task_for}: map a path to the corresponding directory task reference
245             # This structure allows us to quickly determine if a path has an existing
246             # directory task associated with it.
247 78         131 $self->{link_task_for} = {};
248              
249             # N.B.: directory tasks and link tasks are NOT mutually exclusive due
250             # to tree splitting (which involves a remove link task followed by
251             # a create directory task).
252             }
253              
254             =head1 METHODS
255              
256             =head2 plan_unstow(@packages)
257              
258             Plan which symlink/directory creation/removal tasks need to be executed
259             in order to unstow the given packages. Any potential conflicts are then
260             accessible via L.
261              
262             =cut
263              
264             sub plan_unstow {
265 37     37 1 21323 my $self = shift;
266 37         111 my @packages = @_;
267              
268             $self->within_target_do(sub {
269 37     37   79 for my $package (@packages) {
270 37         94 my $path = join_paths($self->{stow_path}, $package);
271 37 50       527 if (not -d $path) {
272 0         0 error("The stow directory $self->{stow_path} does not contain package $package");
273             }
274 37         209 debug(2, "Planning unstow of package $package...");
275 37 100       91 if ($self->{compat}) {
276             $self->unstow_contents_orig(
277             $self->{stow_path},
278 14         41 $package,
279             '.',
280             );
281             }
282             else {
283             $self->unstow_contents(
284             $self->{stow_path},
285 23         72 $package,
286             '.',
287             );
288             }
289 37         193 debug(2, "Planning unstow of package $package... done");
290 37         102 $self->{action_count}++;
291             }
292 37         228 });
293             }
294              
295             =head2 plan_stow(@packages)
296              
297             Plan which symlink/directory creation/removal tasks need to be executed
298             in order to stow the given packages. Any potential conflicts are then
299             accessible via L.
300              
301             =cut
302              
303             sub plan_stow {
304 39     39 1 14239 my $self = shift;
305 39         119 my @packages = @_;
306              
307             $self->within_target_do(sub {
308 39     39   82 for my $package (@packages) {
309 41         104 my $path = join_paths($self->{stow_path}, $package);
310 41 50       591 if (not -d $path) {
311 0         0 error("The stow directory $self->{stow_path} does not contain package $package");
312             }
313 41         256 debug(2, "Planning stow of package $package...");
314             $self->stow_contents(
315             $self->{stow_path},
316 41         145 $package,
317             '.',
318             $path, # source from target
319             );
320 41         164 debug(2, "Planning stow of package $package... done");
321 41         92 $self->{action_count}++;
322             }
323 39         248 });
324             }
325              
326             #===== METHOD ===============================================================
327             # Name : within_target_do()
328             # Purpose : execute code within target directory, preserving cwd
329             # Parameters: $code => anonymous subroutine to execute within target dir
330             # Returns : n/a
331             # Throws : n/a
332             # Comments : This is done to ensure that the consumer of the Stow interface
333             # : doesn't have to worry about (a) what their cwd is, and
334             # : (b) that their cwd might change.
335             #============================================================================
336             sub within_target_do {
337 121     121 0 189 my $self = shift;
338 121         204 my ($code) = @_;
339              
340 121         1009 my $cwd = getcwd();
341             chdir($self->{target})
342 121 50       1041 or error("Cannot chdir to target tree: $self->{target} ($!)");
343 121         1013 debug(3, "cwd now $self->{target}");
344              
345 121         370 $self->$code();
346              
347 121         374 restore_cwd($cwd);
348 121         501 debug(3, "cwd restored to $cwd");
349             }
350              
351             #===== METHOD ===============================================================
352             # Name : stow_contents()
353             # Purpose : stow the contents of the given directory
354             # Parameters: $stow_path => relative path from current (i.e. target) directory
355             # : to the stow dir containing the package to be stowed
356             # : $package => the package whose contents are being stowed
357             # : $target => subpath relative to package directory which needs
358             # : stowing as a symlink at subpath relative to target
359             # : directory.
360             # : $source => relative path from the (sub)dir of target
361             # : to symlink source
362             # Returns : n/a
363             # Throws : a fatal error if directory cannot be read
364             # Comments : stow_node() and stow_contents() are mutually recursive.
365             # : $source and $target are used for creating the symlink
366             # : $path is used for folding/unfolding trees as necessary
367             #============================================================================
368             sub stow_contents {
369 94     94 0 143 my $self = shift;
370 94         202 my ($stow_path, $package, $target, $source) = @_;
371              
372 94         190 my $path = join_paths($stow_path, $package, $target);
373              
374 94 100       225 return if $self->should_skip_target_which_is_stow_dir($target);
375              
376 93         698 my $cwd = getcwd();
377 93         331 my $msg = "Stowing contents of $path (cwd=$cwd)";
378 93         1034 $msg =~ s!$ENV{HOME}(/|$)!~$1!g;
379 93         308 debug(3, $msg);
380 93         289 debug(4, " => $source");
381              
382 93 50       1180 error("stow_contents() called with non-directory path: $path")
383             unless -d $path;
384 93 50       344 error("stow_contents() called with non-directory target: $target")
385             unless $self->is_a_node($target);
386              
387 93 50       2712 opendir my $DIR, $path
388             or error("cannot read directory: $path ($!)");
389 93         1747 my @listing = readdir $DIR;
390 93         900 closedir $DIR;
391              
392             NODE:
393 93         251 for my $node (@listing) {
394 326 100       651 next NODE if $node eq '.';
395 233 100       581 next NODE if $node eq '..';
396 140         394 my $node_target = join_paths($target, $node);
397 140 100       396 next NODE if $self->ignore($stow_path, $package, $node_target);
398              
399 124 100       297 if ($self->{dotfiles}) {
400 7         18 my $adj_node_target = adjust_dotfile($node_target);
401 7         24 debug(4, " Adjusting: $node_target => $adj_node_target");
402 7         11 $node_target = $adj_node_target;
403             }
404              
405             $self->stow_node(
406 124         344 $stow_path,
407             $package,
408             $node_target, # target
409             join_paths($source, $node), # source
410             );
411             }
412             }
413              
414             #===== METHOD ===============================================================
415             # Name : stow_node()
416             # Purpose : stow the given node
417             # Parameters: $stow_path => relative path from current (i.e. target) directory
418             # : to the stow dir containing the node to be stowed
419             # : $package => the package containing the node being stowed
420             # : $target => subpath relative to package directory of node which
421             # : needs stowing as a symlink at subpath relative to
422             # : target directory.
423             # : $source => relative path to symlink source from the dir of target
424             # Returns : n/a
425             # Throws : fatal exception if a conflict arises
426             # Comments : stow_node() and stow_contents() are mutually recursive
427             # : $source and $target are used for creating the symlink
428             # : $path is used for folding/unfolding trees as necessary
429             #============================================================================
430             sub stow_node {
431 124     124 0 192 my $self = shift;
432 124         245 my ($stow_path, $package, $target, $source) = @_;
433              
434 124         237 my $path = join_paths($stow_path, $package, $target);
435              
436 124         450 debug(3, "Stowing $stow_path / $package / $target");
437 124         317 debug(4, " => $source");
438              
439             # Don't try to stow absolute symlinks (they can't be unstowed)
440 124 50       1661 if (-l $source) {
441 0         0 my $second_source = $self->read_a_link($source);
442 0 0       0 if ($second_source =~ m{\A/}) {
443 0         0 $self->conflict(
444             'stow',
445             $package,
446             "source is an absolute symlink $source => $second_source"
447             );
448 0         0 debug(3, "Absolute symlinks cannot be unstowed");
449 0         0 return;
450             }
451             }
452              
453             # Does the target already exist?
454 124 100 100     545 if ($self->is_a_link($target)) {
    100 100        
    100          
455             # Where is the link pointing?
456 19         53 my $existing_source = $self->read_a_link($target);
457 19 50       49 if (not $existing_source) {
458 0         0 error("Could not read link: $target");
459             }
460 19         76 debug(4, " Evaluate existing link: $target => $existing_source");
461              
462             # Does it point to a node under any stow directory?
463 19         67 my ($existing_path, $existing_stow_path, $existing_package) =
464             $self->find_stowed_path($target, $existing_source);
465 19 100       46 if (not $existing_path) {
466 1         9 $self->conflict(
467             'stow',
468             $package,
469             "existing target is not owned by stow: $target"
470             );
471 1         3 return; # XXX #
472             }
473              
474             # Does the existing $target actually point to anything?
475 18 100       52 if ($self->is_a_node($existing_path)) {
476 17 100 66     71 if ($existing_source eq $source) {
    100          
    100          
    100          
477 5         36 debug(2, "--- Skipping $target as it already points to $source");
478             }
479             elsif ($self->defer($target)) {
480 1         5 debug(2, "--- Deferring installation of: $target");
481             }
482             elsif ($self->override($target)) {
483 1         8 debug(2, "--- Overriding installation of: $target");
484 1         4 $self->do_unlink($target);
485 1         3 $self->do_link($source, $target);
486             }
487             elsif ($self->is_a_dir(join_paths(parent($target), $existing_source)) &&
488             $self->is_a_dir(join_paths(parent($target), $source)) ) {
489              
490             # If the existing link points to a directory,
491             # and the proposed new link points to a directory,
492             # then we can unfold (split open) the tree at that point
493              
494 9         38 debug(2, "--- Unfolding $target which was already owned by $existing_package");
495 9         31 $self->do_unlink($target);
496 9         27 $self->do_mkdir($target);
497 9         24 $self->stow_contents(
498             $existing_stow_path,
499             $existing_package,
500             $target,
501             join_paths('..', $existing_source),
502             );
503             $self->stow_contents(
504             $self->{stow_path},
505 9         38 $package,
506             $target,
507             join_paths('..', $source),
508             );
509             }
510             else {
511 1         8 $self->conflict(
512             'stow',
513             $package,
514             "existing target is stowed to a different package: "
515             . "$target => $existing_source"
516             );
517             }
518             }
519             else {
520             # The existing link is invalid, so replace it with a good link
521 1         5 debug(2, "--- replacing invalid link: $path");
522 1         4 $self->do_unlink($target);
523 1         3 $self->do_link($source, $target);
524             }
525             }
526             elsif ($self->is_a_node($target)) {
527 31         116 debug(4, " Evaluate existing node: $target");
528 31 100       81 if ($self->is_a_dir($target)) {
529             $self->stow_contents(
530             $self->{stow_path},
531 25         65 $package,
532             $target,
533             join_paths('..', $source),
534             );
535             }
536             else {
537 6 100       16 if ($self->{adopt}) {
538 2         13 $self->do_mv($target, $path);
539 2         6 $self->do_link($source, $target);
540             }
541             else {
542 4         14 $self->conflict(
543             'stow',
544             $package,
545             "existing target is neither a link nor a directory: $target"
546             );
547             }
548             }
549             }
550             elsif ($self->{'no-folding'} && -d $path && ! -l $path) {
551 10         45 $self->do_mkdir($target);
552             $self->stow_contents(
553             $self->{stow_path},
554 10         27 $package,
555             $target,
556             join_paths('..', $source),
557             );
558             }
559             else {
560 64         188 $self->do_link($source, $target);
561             }
562 123         379 return;
563             }
564              
565             #===== METHOD ===============================================================
566             # Name : should_skip_target_which_is_stow_dir()
567             # Purpose : determine whether target is a stow directory which should
568             # : not be stowed to or unstowed from
569             # Parameters: $target => relative path to symlink target from the current directory
570             # Returns : true iff target is a stow directory
571             # Throws : n/a
572             # Comments : none
573             #============================================================================
574             sub should_skip_target_which_is_stow_dir {
575 228     228 0 322 my $self = shift;
576 228         411 my ($target) = @_;
577              
578             # Don't try to remove anything under a stow directory
579 228 100       492 if ($target eq $self->{stow_path}) {
580 3         34 warn "WARNING: skipping target which was current stow directory $target\n";
581 3         64 return 1;
582             }
583              
584 225 100       411 if ($self->marked_stow_dir($target)) {
585 14         120 warn "WARNING: skipping protected directory $target\n";
586 14         257 return 1;
587             }
588              
589 211         782 debug(4, "$target not protected");
590 211         446 return 0;
591             }
592              
593             sub marked_stow_dir {
594 724     724 0 948 my $self = shift;
595 724         1195 my ($target) = @_;
596 724         1113 for my $f (".stow", ".nonstow") {
597 1432 100       3531 if (-e join_paths($target, $f)) {
598 16         117 debug(4, "$target contained $f");
599 16         69 return 1;
600             }
601             }
602 708         2472 return 0;
603             }
604              
605             #===== METHOD ===============================================================
606             # Name : unstow_contents_orig()
607             # Purpose : unstow the contents of the given directory
608             # Parameters: $stow_path => relative path from current (i.e. target) directory
609             # : to the stow dir containing the package to be unstowed
610             # : $package => the package whose contents are being unstowed
611             # : $target => relative path to symlink target from the current directory
612             # Returns : n/a
613             # Throws : a fatal error if directory cannot be read
614             # Comments : unstow_node_orig() and unstow_contents_orig() are mutually recursive
615             # : Here we traverse the target tree, rather than the source tree.
616             #============================================================================
617             sub unstow_contents_orig {
618 88     88 0 144 my $self = shift;
619 88         179 my ($stow_path, $package, $target) = @_;
620              
621 88         210 my $path = join_paths($stow_path, $package, $target);
622              
623 88 100       207 return if $self->should_skip_target_which_is_stow_dir($target);
624              
625 73         541 my $cwd = getcwd();
626 73         289 my $msg = "Unstowing from $target (compat mode, cwd=$cwd, stow dir=$self->{stow_path})";
627 73         774 $msg =~ s!$ENV{HOME}(/|$)!~$1!g;
628 73         244 debug(3, $msg);
629 73         207 debug(4, " source path is $path");
630             # In compat mode we traverse the target tree not the source tree,
631             # so we're unstowing the contents of /target/foo, there's no
632             # guarantee that the corresponding /stow/mypkg/foo exists.
633 73 50       863 error("unstow_contents_orig() called with non-directory target: $target")
634             unless -d $target;
635              
636 73 50       1987 opendir my $DIR, $target
637             or error("cannot read directory: $target ($!)");
638 73         1289 my @listing = readdir $DIR;
639 73         694 closedir $DIR;
640              
641             NODE:
642 73         227 for my $node (@listing) {
643 268 100       547 next NODE if $node eq '.';
644 195 100       516 next NODE if $node eq '..';
645 122         313 my $node_target = join_paths($target, $node);
646 122 50       341 next NODE if $self->ignore($stow_path, $package, $node_target);
647 122         324 $self->unstow_node_orig($stow_path, $package, $node_target);
648             }
649             }
650              
651             #===== METHOD ===============================================================
652             # Name : unstow_node_orig()
653             # Purpose : unstow the given node
654             # Parameters: $stow_path => relative path from current (i.e. target) directory
655             # : to the stow dir containing the node to be stowed
656             # : $package => the package containing the node being stowed
657             # : $target => relative path to symlink target from the current directory
658             # Returns : n/a
659             # Throws : fatal error if a conflict arises
660             # Comments : unstow_node() and unstow_contents() are mutually recursive
661             #============================================================================
662             sub unstow_node_orig {
663 122     122 0 184 my $self = shift;
664 122         214 my ($stow_path, $package, $target) = @_;
665              
666 122         282 my $path = join_paths($stow_path, $package, $target);
667              
668 122         347 debug(3, "Unstowing $target (compat mode)");
669 122         290 debug(4, " source path is $path");
670              
671             # Does the target exist?
672 122 100       289 if ($self->is_a_link($target)) {
    100          
    50          
673 47         176 debug(4, " Evaluate existing link: $target");
674              
675             # Where is the link pointing?
676 47         103 my $existing_source = $self->read_a_link($target);
677 47 50       92 if (not $existing_source) {
678 0         0 error("Could not read link: $target");
679             }
680              
681             # Does it point to a node under any stow directory?
682 47         145 my ($existing_path, $existing_stow_path, $existing_package) =
683             $self->find_stowed_path($target, $existing_source);
684 47 100       95 if (not $existing_path) {
685             # We're traversing the target tree not the package tree,
686             # so we definitely expect to find stuff not owned by stow.
687             # Therefore we can't flag a conflict.
688 14         37 return; # XXX #
689             }
690              
691             # Does the existing $target actually point to anything?
692 33 100       441 if (-e $existing_path) {
693             # Does link point to the right place?
694 32 100       146 if ($existing_path eq $path) {
    100          
695 4         13 $self->do_unlink($target);
696             }
697             elsif ($self->override($target)) {
698 1         7 debug(2, "--- overriding installation of: $target");
699 1         6 $self->do_unlink($target);
700             }
701             # else leave it alone
702             }
703             else {
704 1         7 debug(2, "--- removing invalid link into a stow directory: $path");
705 1         3 $self->do_unlink($target);
706             }
707             }
708             elsif (-d $target) {
709 74         308 $self->unstow_contents_orig($stow_path, $package, $target);
710              
711             # This action may have made the parent directory foldable
712 74 100       179 if (my $parent = $self->foldable($target)) {
713 3         16 $self->fold_tree($target, $parent);
714             }
715             }
716             elsif (-e $target) {
717 1         10 $self->conflict(
718             'unstow',
719             $package,
720             "existing target is neither a link nor a directory: $target",
721             );
722             }
723             else {
724 0         0 debug(2, "$target did not exist to be unstowed");
725             }
726 108         293 return;
727             }
728              
729             #===== METHOD ===============================================================
730             # Name : unstow_contents()
731             # Purpose : unstow the contents of the given directory
732             # Parameters: $stow_path => relative path from current (i.e. target) directory
733             # : to the stow dir containing the package to be unstowed
734             # : $package => the package whose contents are being unstowed
735             # : $target => relative path to symlink target from the current directory
736             # Returns : n/a
737             # Throws : a fatal error if directory cannot be read
738             # Comments : unstow_node() and unstow_contents() are mutually recursive
739             # : Here we traverse the source tree, rather than the target tree.
740             #============================================================================
741             sub unstow_contents {
742 46     46 0 89 my $self = shift;
743 46         101 my ($stow_path, $package, $target) = @_;
744              
745 46         108 my $path = join_paths($stow_path, $package, $target);
746              
747 46 100       109 return if $self->should_skip_target_which_is_stow_dir($target);
748              
749 45         338 my $cwd = getcwd();
750 45         199 my $msg = "Unstowing from $target (cwd=$cwd, stow dir=$self->{stow_path})";
751 45         408 $msg =~ s!$ENV{HOME}/!~/!g;
752 45         154 debug(3, $msg);
753 45         128 debug(4, " source path is $path");
754             # We traverse the source tree not the target tree, so $path must exist.
755 45 50       554 error("unstow_contents() called with non-directory path: $path")
756             unless -d $path;
757             # When called at the top level, $target should exist. And
758             # unstow_node() should only call this via mutual recursion if
759             # $target exists.
760 45 50       171 error("unstow_contents() called with invalid target: $target")
761             unless $self->is_a_node($target);
762              
763 45 50       1316 opendir my $DIR, $path
764             or error("cannot read directory: $path ($!)");
765 45         787 my @listing = readdir $DIR;
766 45         408 closedir $DIR;
767              
768             NODE:
769 45         122 for my $node (@listing) {
770 154 100       329 next NODE if $node eq '.';
771 109 100       203 next NODE if $node eq '..';
772 64         176 my $node_target = join_paths($target, $node);
773 64 100       180 next NODE if $self->ignore($stow_path, $package, $node_target);
774              
775 56 100       149 if ($self->{dotfiles}) {
776 5         12 my $adj_node_target = adjust_dotfile($node_target);
777 5         18 debug(4, " Adjusting: $node_target => $adj_node_target");
778 5         8 $node_target = $adj_node_target;
779             }
780              
781 56         187 $self->unstow_node($stow_path, $package, $node_target);
782             }
783 45 50       534 if (-d $target) {
784 45         167 $self->cleanup_invalid_links($target);
785             }
786             }
787              
788             #===== METHOD ===============================================================
789             # Name : unstow_node()
790             # Purpose : unstow the given node
791             # Parameters: $stow_path => relative path from current (i.e. target) directory
792             # : to the stow dir containing the node to be stowed
793             # : $package => the package containing the node being unstowed
794             # : $target => relative path to symlink target from the current directory
795             # Returns : n/a
796             # Throws : fatal error if a conflict arises
797             # Comments : unstow_node() and unstow_contents() are mutually recursive
798             #============================================================================
799             sub unstow_node {
800 56     56 0 111 my $self = shift;
801 56         113 my ($stow_path, $package, $target) = @_;
802              
803 56         135 my $path = join_paths($stow_path, $package, $target);
804              
805 56         178 debug(3, "Unstowing $path");
806 56         154 debug(4, " target is $target");
807              
808             # Does the target exist?
809 56 100       132 if ($self->is_a_link($target)) {
    100          
810 27         78 debug(4, " Evaluate existing link: $target");
811              
812             # Where is the link pointing?
813 27         63 my $existing_source = $self->read_a_link($target);
814 27 50       57 if (not $existing_source) {
815 0         0 error("Could not read link: $target");
816             }
817              
818 27 50       70 if ($existing_source =~ m{\A/}) {
819 0         0 warn "Ignoring an absolute symlink: $target => $existing_source\n";
820 0         0 return; # XXX #
821             }
822              
823             # Does it point to a node under any stow directory?
824 27         74 my ($existing_path, $existing_stow_path, $existing_package) =
825             $self->find_stowed_path($target, $existing_source);
826 27 100       66 if (not $existing_path) {
827 1         9 $self->conflict(
828             'unstow',
829             $package,
830             "existing target is not owned by stow: $target => $existing_source"
831             );
832 1         3 return; # XXX #
833             }
834              
835             # Does the existing $target actually point to anything?
836 26 100       355 if (-e $existing_path) {
837             # Does link points to the right place?
838              
839             # Adjust for dotfile if necessary.
840 25 100       85 if ($self->{dotfiles}) {
841 5         16 $existing_path = adjust_dotfile($existing_path);
842             }
843              
844 25 100       59 if ($existing_path eq $path) {
845 22         72 $self->do_unlink($target);
846             }
847              
848             # XXX we quietly ignore links that are stowed to a different
849             # package.
850              
851             #elsif (defer($target)) {
852             # debug(2, "--- deferring to installation of: $target");
853             #}
854             #elsif ($self->override($target)) {
855             # debug(2, "--- overriding installation of: $target");
856             # $self->do_unlink($target);
857             #}
858             #else {
859             # $self->conflict(
860             # 'unstow',
861             # $package,
862             # "existing target is stowed to a different package: "
863             # . "$target => $existing_source"
864             # );
865             #}
866             }
867             else {
868 1         13 debug(2, "--- removing invalid link into a stow directory: $path");
869 1         6 $self->do_unlink($target);
870             }
871             }
872             elsif (-e $target) {
873 24         107 debug(4, " Evaluate existing node: $target");
874 24 100       271 if (-d $target) {
875 23         120 $self->unstow_contents($stow_path, $package, $target);
876              
877             # This action may have made the parent directory foldable
878 23 100       67 if (my $parent = $self->foldable($target)) {
879 4         14 $self->fold_tree($target, $parent);
880             }
881             }
882             else {
883 1         9 $self->conflict(
884             'unstow',
885             $package,
886             "existing target is neither a link nor a directory: $target",
887             );
888             }
889             }
890             else {
891 5         35 debug(2, "$target did not exist to be unstowed");
892             }
893 55         140 return;
894             }
895              
896             #===== METHOD ===============================================================
897             # Name : path_owned_by_package()
898             # Purpose : determine whether the given link points to a member of a
899             # : stowed package
900             # Parameters: $target => path to a symbolic link under current directory
901             # : $source => where that link points to
902             # Returns : the package iff link is owned by stow, otherwise ''
903             # Throws : n/a
904             # Comments : lossy wrapper around find_stowed_path()
905             #============================================================================
906             sub path_owned_by_package {
907 29     29 0 82 my $self = shift;
908 29         84 my ($target, $source) = @_;
909              
910 29         78 my ($path, $stow_path, $package) =
911             $self->find_stowed_path($target, $source);
912 29         117 return $package;
913             }
914              
915             #===== METHOD ===============================================================
916             # Name : find_stowed_path()
917             # Purpose : determine whether the given link points to a member of a
918             # : stowed package
919             # Parameters: $target => path to a symbolic link under current directory.
920             # : Must share a common prefix with $self->{stow_path}
921             # : $source => where that link points to (needed because link
922             # : might not exist yet due to two-phase approach,
923             # : so we can't just call readlink()). This must be
924             # : expressed relative to (the directory containing)
925             # : $target.
926             # Returns : ($path, $stow_path, $package) where $path and $stow_path are
927             # : relative from the current (i.e. target) directory. $path
928             # : is the full relative path, $stow_path is the relative path
929             # : to the stow directory, and $package is the name of the package.
930             # : or ('', '', '') if link is not owned by stow
931             # Throws : n/a
932             # Comments : Allow for stow dir not being under target dir.
933             # : We could put more logic under here for multiple stow dirs.
934             #============================================================================
935             sub find_stowed_path {
936 128     128 0 5445 my $self = shift;
937 128         274 my ($target, $source) = @_;
938              
939             # Evaluate softlink relative to its target
940 128         307 my $path = join_paths(parent($target), $source);
941 128         467 debug(4, " is path $path owned by stow?");
942              
943             # Search for .stow files - this allows us to detect links
944             # owned by stow directories other than the current one.
945 128         199 my $dir = '';
946 128         481 my @path = split m{/+}, $path;
947 128         401 for my $i (0 .. $#path) {
948 499         874 my $part = $path[$i];
949 499         1035 $dir = join_paths($dir, $part);
950 499 100       1123 if ($self->marked_stow_dir($dir)) {
951             # FIXME - not sure if this can ever happen
952 2 50       10 internal_error("find_stowed_path() called directly on stow dir")
953             if $i == $#path;
954              
955 2         16 debug(4, " yes - $dir was marked as a stow dir");
956 2         10 my $package = $path[$i + 1];
957 2         13 return ($path, $dir, $package);
958             }
959             }
960              
961             # If no .stow file was found, we need to find out whether it's
962             # owned by the current stow directory, in which case $path will be
963             # a prefix of $self->{stow_path}.
964 126 50 25     681 if (substr($path, 0, 1) eq '/' xor substr($self->{stow_path}, 0, 1) eq '/')
965             {
966 0         0 warn "BUG in find_stowed_path? Absolute/relative mismatch between " .
967             "Stow dir $self->{stow_path} and path $path";
968             }
969              
970 126         555 my @stow_path = split m{/+}, $self->{stow_path};
971              
972             # Strip off common prefixes until one is empty
973 126   100     524 while (@path && @stow_path) {
974 238 100       837 if ((shift @path) ne (shift @stow_path)) {
975 37         193 debug(4, " no - either $path not under $self->{stow_path} or vice-versa");
976 37         153 return ('', '', '');
977             }
978             }
979              
980 89 100       192 if (@stow_path) { # @path must be empty
981 1         6 debug(4, " no - $path is not under $self->{stow_path}");
982 1         6 return ('', '', '');
983             }
984              
985 88         196 my $package = shift @path;
986              
987 88         317 debug(4, " yes - by $package in " . join_paths(@path));
988 88         356 return ($path, $self->{stow_path}, $package);
989             }
990              
991             #===== METHOD ================================================================
992             # Name : cleanup_invalid_links()
993             # Purpose : clean up invalid links that may block folding
994             # Parameters: $dir => path to directory to check
995             # Returns : n/a
996             # Throws : no exceptions
997             # Comments : removing files from a stowed package is probably a bad practice
998             # : so this kind of clean up is not _really_ stow's responsibility;
999             # : however, failing to clean up can block tree folding, so we'll do
1000             # : it anyway
1001             #=============================================================================
1002             sub cleanup_invalid_links {
1003 48     48 0 89 my $self = shift;
1004 48         108 my ($dir) = @_;
1005              
1006 48 50       449 if (not -d $dir) {
1007 0         0 error("cleanup_invalid_links() called with a non-directory: $dir");
1008             }
1009              
1010 48 50       1285 opendir my $DIR, $dir
1011             or error("cannot read directory: $dir ($!)");
1012 48         939 my @listing = readdir $DIR;
1013 48         470 closedir $DIR;
1014              
1015             NODE:
1016 48         178 for my $node (@listing) {
1017 311 100       745 next NODE if $node eq '.';
1018 263 100       478 next NODE if $node eq '..';
1019              
1020 215         577 my $node_path = join_paths($dir, $node);
1021              
1022 215 100 100     2667 if (-l $node_path and not exists $self->{link_task_for}{$node_path}) {
1023              
1024             # Where is the link pointing?
1025             # (don't use read_a_link() here)
1026 65         519 my $source = readlink($node_path);
1027 65 50       181 if (not $source) {
1028 0         0 error("Could not read link $node_path");
1029             }
1030              
1031 65 100 100     185 if (
1032             not -e join_paths($dir, $source) and # bad link
1033             $self->path_owned_by_package($node_path, $source) # owned by stow
1034             ){
1035 1         7 debug(2, "--- removing stale link: $node_path => " .
1036             join_paths($dir, $source));
1037 1         4 $self->do_unlink($node_path);
1038             }
1039             }
1040             }
1041 48         318 return;
1042             }
1043              
1044              
1045             #===== METHOD ===============================================================
1046             # Name : foldable()
1047             # Purpose : determine whether a tree can be folded
1048             # Parameters: $target => path to a directory
1049             # Returns : path to the parent dir iff the tree can be safely folded
1050             # Throws : n/a
1051             # Comments : the path returned is relative to the parent of $target,
1052             # : that is, it can be used as the source for a replacement symlink
1053             #============================================================================
1054             sub foldable {
1055 101     101 0 756 my $self = shift;
1056 101         194 my ($target) = @_;
1057              
1058 101         355 debug(3, "--- Is $target foldable?");
1059 101 100       271 if ($self->{'no-folding'}) {
1060 6         14 debug(3, "--- no because --no-folding enabled");
1061 6         18 return '';
1062             }
1063              
1064 95 50       2356 opendir my $DIR, $target
1065             or error(qq{Cannot read directory "$target" ($!)\n});
1066 95         1499 my @listing = readdir $DIR;
1067 95         822 closedir $DIR;
1068              
1069 95         237 my $parent = '';
1070             NODE:
1071 95         180 for my $node (@listing) {
1072              
1073 180 100       370 next NODE if $node eq '.';
1074 130 100       248 next NODE if $node eq '..';
1075              
1076 80         235 my $path = join_paths($target, $node);
1077              
1078             # Skip nodes scheduled for removal
1079 80 100       231 next NODE if not $self->is_a_node($path);
1080              
1081             # If it's not a link then we can't fold its parent
1082 71 100       185 return '' if not $self->is_a_link($path);
1083              
1084             # Where is the link pointing?
1085 32         89 my $source = $self->read_a_link($path);
1086 32 50       73 if (not $source) {
1087 0         0 error("Could not read link $path");
1088             }
1089 32 100       89 if ($parent eq '') {
    50          
1090 25         66 $parent = parent($source)
1091             }
1092             elsif ($parent ne parent($source)) {
1093 7         48 return '';
1094             }
1095             }
1096 49 100       262 return '' if not $parent;
1097              
1098             # If we get here then all nodes inside $target are links, and those links
1099             # point to nodes inside the same directory.
1100              
1101             # chop of leading '..' to get the path to the common parent directory
1102             # relative to the parent of our $target
1103 10         45 $parent =~ s{\A\.\./}{};
1104              
1105             # If the resulting path is owned by stow, we can fold it
1106 10 100       37 if ($self->path_owned_by_package($target, $parent)) {
1107 8         43 debug(3, "--- $target is foldable");
1108 8         45 return $parent;
1109             }
1110             else {
1111 2         10 return '';
1112             }
1113             }
1114              
1115             #===== METHOD ===============================================================
1116             # Name : fold_tree()
1117             # Purpose : fold the given tree
1118             # Parameters: $source => link to the folded tree source
1119             # : $target => directory that we will replace with a link to $source
1120             # Returns : n/a
1121             # Throws : none
1122             # Comments : only called iff foldable() is true so we can remove some checks
1123             #============================================================================
1124             sub fold_tree {
1125 7     7 0 13 my $self = shift;
1126 7         24 my ($target, $source) = @_;
1127              
1128 7         29 debug(3, "--- Folding tree: $target => $source");
1129              
1130 7 50       203 opendir my $DIR, $target
1131             or error(qq{Cannot read directory "$target" ($!)\n});
1132 7         126 my @listing = readdir $DIR;
1133 7         67 closedir $DIR;
1134              
1135             NODE:
1136 7         31 for my $node (@listing) {
1137 23 100       60 next NODE if $node eq '.';
1138 16 100       33 next NODE if $node eq '..';
1139 9 100       33 next NODE if not $self->is_a_node(join_paths($target, $node));
1140 7         26 $self->do_unlink(join_paths($target, $node));
1141             }
1142 7         47 $self->do_rmdir($target);
1143 7         22 $self->do_link($source, $target);
1144 7         24 return;
1145             }
1146              
1147              
1148             #===== METHOD ===============================================================
1149             # Name : conflict()
1150             # Purpose : handle conflicts in stow operations
1151             # Parameters: $package => the package involved with the conflicting operation
1152             # : $message => a description of the conflict
1153             # Returns : n/a
1154             # Throws : none
1155             # Comments : none
1156             #============================================================================
1157             sub conflict {
1158 9     9 0 17 my $self = shift;
1159 9         22 my ($action, $package, $message) = @_;
1160              
1161 9         47 debug(2, "CONFLICT when ${action}ing $package: $message");
1162 9   100     72 $self->{conflicts}{$action}{$package} ||= [];
1163 9         16 push @{ $self->{conflicts}{$action}{$package} }, $message;
  9         32  
1164 9         16 $self->{conflict_count}++;
1165              
1166 9         18 return;
1167             }
1168              
1169             =head2 get_conflicts()
1170              
1171             Returns a nested hash of all potential conflicts discovered: the keys
1172             are actions ('stow' or 'unstow'), and the values are hashrefs whose
1173             keys are stow package names and whose values are conflict
1174             descriptions, e.g.:
1175              
1176             (
1177             stow => {
1178             perl => [
1179             "existing target is not owned by stow: bin/a2p"
1180             "existing target is neither a link nor a directory: bin/perl"
1181             ]
1182             }
1183             )
1184              
1185             =cut
1186              
1187             sub get_conflicts {
1188 22     22 1 146 my $self = shift;
1189 22         37 return %{ $self->{conflicts} };
  22         125  
1190             }
1191              
1192             =head2 get_conflict_count()
1193              
1194             Returns the number of conflicts found.
1195              
1196             =cut
1197              
1198             sub get_conflict_count {
1199 51     51 1 298 my $self = shift;
1200 51         178 return $self->{conflict_count};
1201             }
1202              
1203             =head2 get_tasks()
1204              
1205             Returns a list of all symlink/directory creation/removal tasks.
1206              
1207             =cut
1208              
1209             sub get_tasks {
1210 26     26 1 236 my $self = shift;
1211 26         37 return @{ $self->{tasks} };
  26         162  
1212             }
1213              
1214             =head2 get_action_count()
1215              
1216             Returns the number of actions planned for this Stow instance.
1217              
1218             =cut
1219              
1220             sub get_action_count {
1221 0     0 1 0 my $self = shift;
1222 0         0 return $self->{action_count};
1223             }
1224              
1225             #===== METHOD ================================================================
1226             # Name : ignore
1227             # Purpose : determine if the given path matches a regex in our ignore list
1228             # Parameters: $stow_path => the stow directory containing the package
1229             # : $package => the package containing the path
1230             # : $target => the path to check against the ignore list
1231             # : relative to its package directory
1232             # Returns : true iff the path should be ignored
1233             # Throws : no exceptions
1234             # Comments : none
1235             #=============================================================================
1236             sub ignore {
1237 597     597 1 168023 my $self = shift;
1238 597         1266 my ($stow_path, $package, $target) = @_;
1239              
1240 597 50       1287 internal_error(__PACKAGE__ . "::ignore() called with empty target")
1241             unless length $target;
1242              
1243 597         777 for my $suffix (@{ $self->{ignore} }) {
  597         1270  
1244 50 100       492 if ($target =~ m/$suffix/) {
1245 4         27 debug(4, " Ignoring path $target due to --ignore=$suffix");
1246 4         15 return 1;
1247             }
1248             }
1249              
1250 593         1444 my $package_dir = join_paths($stow_path, $package);
1251 593         1393 my ($path_regexp, $segment_regexp) =
1252             $self->get_ignore_regexps($package_dir);
1253 591 50       2736 debug(5, " Ignore list regexp for paths: " .
1254             (defined $path_regexp ? "/$path_regexp/" : "none"));
1255 591 100       1991 debug(5, " Ignore list regexp for segments: " .
1256             (defined $segment_regexp ? "/$segment_regexp/" : "none"));
1257              
1258 591 100 66     4769 if (defined $path_regexp and "/$target" =~ $path_regexp) {
1259 25         149 debug(4, " Ignoring path /$target");
1260 25         79 return 1;
1261             }
1262              
1263 566         2173 (my $basename = $target) =~ s!.+/!!;
1264 566 100 100     3089 if (defined $segment_regexp and $basename =~ $segment_regexp) {
1265 28         123 debug(4, " Ignoring path segment $basename");
1266 28         109 return 1;
1267             }
1268              
1269 538         1681 debug(5, " Not ignoring $target");
1270 538         1376 return 0;
1271             }
1272              
1273             sub get_ignore_regexps {
1274 593     593 0 802 my $self = shift;
1275 593         932 my ($dir) = @_;
1276              
1277             # N.B. the local and global stow ignore files have to have different
1278             # names so that:
1279             # 1. the global one can be a symlink to within a stow
1280             # package, managed by stow itself, and
1281             # 2. the local ones can be ignored via hardcoded logic in
1282             # GlobsToRegexp(), so that they always stay within their stow packages.
1283              
1284 593         1125 my $local_stow_ignore = join_paths($dir, $LOCAL_IGNORE_FILE);
1285 593         1472 my $global_stow_ignore = join_paths($ENV{HOME}, $GLOBAL_IGNORE_FILE);
1286              
1287 593         1131 for my $file ($local_stow_ignore, $global_stow_ignore) {
1288 962 100       11787 if (-e $file) {
1289 266         1385 debug(5, " Using ignore file: $file");
1290 266         747 return $self->get_ignore_regexps_from_file($file);
1291             }
1292             else {
1293 696         3039 debug(5, " $file didn't exist");
1294             }
1295             }
1296              
1297 327         730 debug(4, " Using built-in ignore list");
1298 327         827 return @default_global_ignore_regexps;
1299             }
1300              
1301             my %ignore_file_regexps;
1302              
1303             sub get_ignore_regexps_from_file {
1304 266     266 0 402 my $self = shift;
1305 266         437 my ($file) = @_;
1306              
1307 266 100       681 if (exists $ignore_file_regexps{$file}) {
1308 244         718 debug(4, " Using memoized regexps from $file");
1309 244         337 return @{ $ignore_file_regexps{$file} };
  244         933  
1310             }
1311              
1312 22 50       638 if (! open(REGEXPS, $file)) {
1313 0         0 debug(4, " Failed to open $file: $!");
1314 0         0 return undef;
1315             }
1316              
1317 22         119 my @regexps = $self->get_ignore_regexps_from_fh(\*REGEXPS);
1318 20         217 close(REGEXPS);
1319              
1320 20         100 $ignore_file_regexps{$file} = [ @regexps ];
1321 20         93 return @regexps;
1322             }
1323              
1324             =head2 invalidate_memoized_regexp($file)
1325              
1326             For efficiency of performance, regular expressions are compiled from
1327             each ignore list file the first time it is used by the Stow process,
1328             and then memoized for future use. If you expect the contents of these
1329             files to change during a single run, you will need to invalidate the
1330             memoized value from this cache. This method allows you to do that.
1331              
1332             =cut
1333              
1334             sub invalidate_memoized_regexp {
1335 21     21 1 3648 my $self = shift;
1336 21         61 my ($file) = @_;
1337 21 100       68 if (exists $ignore_file_regexps{$file}) {
1338 18         84 debug(4, " Invalidated memoized regexp for $file");
1339 18         83 delete $ignore_file_regexps{$file};
1340             }
1341             else {
1342 3         18 debug(2, " WARNING: no memoized regexp for $file to invalidate");
1343             }
1344             }
1345              
1346             sub get_ignore_regexps_from_fh {
1347 36     36 0 77 my $self = shift;
1348 36         84 my ($fh) = @_;
1349 36         62 my %regexps;
1350 36         443 while (<$fh>) {
1351 338         500 chomp;
1352 338         566 s/^\s+//;
1353 338         655 s/\s+$//;
1354 338 100 100     1219 next if /^#/ or length($_) == 0;
1355 240         445 s/\s+#.+//; # strip comments to right of pattern
1356 240         372 s/\\#/#/g;
1357 240         1016 $regexps{$_}++;
1358             }
1359              
1360             # Local ignore lists should *always* stay within the stow directory,
1361             # because this is the only place stow looks for them.
1362 36         213 $regexps{"^/\Q$LOCAL_IGNORE_FILE\E\$"}++;
1363              
1364 36         235 return $self->compile_ignore_regexps(%regexps);
1365             }
1366              
1367             sub compile_ignore_regexps {
1368 36     36 0 81 my $self = shift;
1369 36         160 my (%regexps) = @_;
1370              
1371 36         69 my @segment_regexps;
1372             my @path_regexps;
1373 36         133 for my $regexp (keys %regexps) {
1374 276 100       499 if (index($regexp, '/') < 0) {
1375             # No / found in regexp, so use it for matching against basename
1376 187         400 push @segment_regexps, $regexp;
1377             }
1378             else {
1379             # / found in regexp, so use it for matching against full path
1380 89         347 push @path_regexps, $regexp;
1381             }
1382             }
1383              
1384 36         140 my $segment_regexp = join '|', @segment_regexps;
1385 36         92 my $path_regexp = join '|', @path_regexps;
1386 36 100       182 $segment_regexp = @segment_regexps ?
1387             $self->compile_regexp("^($segment_regexp)\$") : undef;
1388 35 50       174 $path_regexp = @path_regexps ?
1389             $self->compile_regexp("(^|/)($path_regexp)(/|\$)") : undef;
1390              
1391 34         233 return ($path_regexp, $segment_regexp);
1392             }
1393              
1394             sub compile_regexp {
1395 62     62 0 121 my $self = shift;
1396 62         115 my ($regexp) = @_;
1397 62         93 my $compiled = eval { qr/$regexp/ };
  62         2271  
1398 62 100       213 die "Failed to compile regexp: $@\n" if $@;
1399 60         149 return $compiled;
1400             }
1401              
1402             sub get_default_global_ignore_regexps {
1403 14     14 0 38 my $class = shift;
1404             # Bootstrap issue - first time we stow, we will be stowing
1405             # .cvsignore so it might not exist in ~ yet, or if it does, it could
1406             # be an old version missing the entries we need. So we make sure
1407             # they are there by hardcoding some crucial entries.
1408 14         59 return $class->get_ignore_regexps_from_fh(\*DATA);
1409             }
1410              
1411             #===== METHOD ================================================================
1412             # Name : defer
1413             # Purpose : determine if the given path matches a regex in our defer list
1414             # Parameters: $path
1415             # Returns : Boolean
1416             # Throws : no exceptions
1417             # Comments : none
1418             #=============================================================================
1419             sub defer {
1420 16     16 1 41 my $self = shift;
1421 16         28 my ($path) = @_;
1422              
1423 16         26 for my $prefix (@{ $self->{defer} }) {
  16         44  
1424 8 100       109 return 1 if $path =~ m/$prefix/;
1425             }
1426 13         45 return 0;
1427             }
1428              
1429             #===== METHOD ================================================================
1430             # Name : override
1431             # Purpose : determine if the given path matches a regex in our override list
1432             # Parameters: $path
1433             # Returns : Boolean
1434             # Throws : no exceptions
1435             # Comments : none
1436             #=============================================================================
1437             sub override {
1438 39     39 1 68 my $self = shift;
1439 39         83 my ($path) = @_;
1440              
1441 39         51 for my $regex (@{ $self->{override} }) {
  39         106  
1442 6 100       87 return 1 if $path =~ m/$regex/;
1443             }
1444 37         114 return 0;
1445             }
1446              
1447             ##############################################################################
1448             #
1449             # The following code provides the abstractions that allow us to defer operating
1450             # on the filesystem until after all potential conflcits have been assessed.
1451             #
1452             ##############################################################################
1453              
1454             #===== METHOD ===============================================================
1455             # Name : process_tasks()
1456             # Purpose : process each task in the tasks list
1457             # Parameters: none
1458             # Returns : n/a
1459             # Throws : fatal error if tasks list is corrupted or a task fails
1460             # Comments : none
1461             #============================================================================
1462             sub process_tasks {
1463 45     45 0 1678 my $self = shift;
1464              
1465 45         139 debug(2, "Processing tasks...");
1466              
1467             # Strip out all tasks with a skip action
1468 45         76 $self->{tasks} = [ grep { $_->{action} ne 'skip' } @{ $self->{tasks} } ];
  137         318  
  45         115  
1469              
1470 45 50       110 if (not @{ $self->{tasks} }) {
  45         122  
1471 0         0 return;
1472             }
1473              
1474             $self->within_target_do(sub {
1475 45     45   77 for my $task (@{ $self->{tasks} }) {
  45         108  
1476 130         293 $self->process_task($task);
1477             }
1478 45         260 });
1479              
1480 45         255 debug(2, "Processing tasks... done");
1481             }
1482              
1483             #===== METHOD ===============================================================
1484             # Name : process_task()
1485             # Purpose : process a single task
1486             # Parameters: $task => the task to process
1487             # Returns : n/a
1488             # Throws : fatal error if task fails
1489             # Comments : Must run from within target directory.
1490             # : Task involve either creating or deleting dirs and symlinks
1491             # : an action is set to 'skip' if it is found to be redundant
1492             #============================================================================
1493             sub process_task {
1494 130     130 0 209 my $self = shift;
1495 130         209 my ($task) = @_;
1496              
1497 130 100       367 if ($task->{action} eq 'create') {
    100          
    50          
1498 85 100       241 if ($task->{type} eq 'dir') {
    50          
1499 19 50       851 mkdir($task->{path}, 0777)
1500             or error("Could not create directory: $task->{path} ($!)");
1501 19         88 return;
1502             }
1503             elsif ($task->{type} eq 'link') {
1504             symlink $task->{source}, $task->{path}
1505             or error(
1506             "Could not create symlink: %s => %s ($!)",
1507             $task->{path},
1508             $task->{source}
1509 66 50       1998 );
1510 66         272 return;
1511             }
1512             }
1513             elsif ($task->{action} eq 'remove') {
1514 43 100       136 if ($task->{type} eq 'dir') {
    50          
1515             rmdir $task->{path}
1516 5 50       259 or error("Could not remove directory: $task->{path} ($!)");
1517 5         23 return;
1518             }
1519             elsif ($task->{type} eq 'link') {
1520             unlink $task->{path}
1521 38 50       1422 or error("Could not remove link: $task->{path} ($!)");
1522 38         198 return;
1523             }
1524             }
1525             elsif ($task->{action} eq 'move') {
1526 2 50       7 if ($task->{type} eq 'file') {
1527             # rename() not good enough, since the stow directory
1528             # might be on a different filesystem to the target.
1529             move $task->{path}, $task->{dest}
1530 2 50       17 or error("Could not move $task->{path} -> $task->{dest} ($!)");
1531 2         345 return;
1532             }
1533             }
1534              
1535             # Should never happen.
1536 0         0 internal_error("bad task action: $task->{action}");
1537             }
1538              
1539             #===== METHOD ===============================================================
1540             # Name : link_task_action()
1541             # Purpose : finds the link task action for the given path, if there is one
1542             # Parameters: $path
1543             # Returns : 'remove', 'create', or '' if there is no action
1544             # Throws : a fatal exception if an invalid action is found
1545             # Comments : none
1546             #============================================================================
1547             sub link_task_action {
1548 849     849 0 1161 my $self = shift;
1549 849         1237 my ($path) = @_;
1550              
1551 849 100       1861 if (! exists $self->{link_task_for}{$path}) {
1552 815         2353 debug(4, " link_task_action($path): no task");
1553 815         3230 return '';
1554             }
1555              
1556 34         81 my $action = $self->{link_task_for}{$path}->{action};
1557 34 50 66     123 internal_error("bad task action: $action")
1558             unless $action eq 'remove' or $action eq 'create';
1559              
1560 34         123 debug(4, " link_task_action($path): link task exists with action $action");
1561 34         78 return $action;
1562             }
1563              
1564             #===== METHOD ===============================================================
1565             # Name : dir_task_action()
1566             # Purpose : finds the dir task action for the given path, if there is one
1567             # Parameters: $path
1568             # Returns : 'remove', 'create', or '' if there is no action
1569             # Throws : a fatal exception if an invalid action is found
1570             # Comments : none
1571             #============================================================================
1572             sub dir_task_action {
1573 400     400 0 536 my $self = shift;
1574 400         561 my ($path) = @_;
1575              
1576 400 100       861 if (! exists $self->{dir_task_for}{$path}) {
1577 370         1029 debug(4, " dir_task_action($path): no task");
1578 370         696 return '';
1579             }
1580              
1581 30         56 my $action = $self->{dir_task_for}{$path}->{action};
1582 30 50 66     115 internal_error("bad task action: $action")
1583             unless $action eq 'remove' or $action eq 'create';
1584              
1585 30         91 debug(4, " dir_task_action($path): dir task exists with action $action");
1586 30         61 return $action;
1587             }
1588              
1589             #===== METHOD ===============================================================
1590             # Name : parent_link_scheduled_for_removal()
1591             # Purpose : determine whether the given path or any parent thereof
1592             # : is a link scheduled for removal
1593             # Parameters: $path
1594             # Returns : Boolean
1595             # Throws : none
1596             # Comments : none
1597             #============================================================================
1598             sub parent_link_scheduled_for_removal {
1599 478     478 0 653 my $self = shift;
1600 478         730 my ($path) = @_;
1601              
1602 478         605 my $prefix = '';
1603 478         1685 for my $part (split m{/+}, $path) {
1604 839         1638 $prefix = join_paths($prefix, $part);
1605 839         2489 debug(4, " parent_link_scheduled_for_removal($path): prefix $prefix");
1606 839 100 66     2066 if (exists $self->{link_task_for}{$prefix} and
1607             $self->{link_task_for}{$prefix}->{action} eq 'remove') {
1608 9         29 debug(4, " parent_link_scheduled_for_removal($path): link scheduled for removal");
1609 9         44 return 1;
1610             }
1611             }
1612              
1613 469         1336 debug(4, " parent_link_scheduled_for_removal($path): returning false");
1614 469         1098 return 0;
1615             }
1616              
1617             #===== METHOD ===============================================================
1618             # Name : is_a_link()
1619             # Purpose : determine if the given path is a current or planned link
1620             # Parameters: $path
1621             # Returns : Boolean
1622             # Throws : none
1623             # Comments : returns false if an existing link is scheduled for removal
1624             # : and true if a non-existent link is scheduled for creation
1625             #============================================================================
1626             sub is_a_link {
1627 373     373 0 556 my $self = shift;
1628 373         628 my ($path) = @_;
1629 373         1146 debug(4, " is_a_link($path)");
1630              
1631 373 100       837 if (my $action = $self->link_task_action($path)) {
1632 7 50       26 if ($action eq 'remove') {
    50          
1633 0         0 debug(4, " is_a_link($path): returning 0 (remove action found)");
1634 0         0 return 0;
1635             }
1636             elsif ($action eq 'create') {
1637 7         24 debug(4, " is_a_link($path): returning 1 (create action found)");
1638 7         21 return 1;
1639             }
1640             }
1641              
1642 366 100       4618 if (-l $path) {
1643             # Check if any of its parent are links scheduled for removal
1644             # (need this for edge case during unfolding)
1645 119         606 debug(4, " is_a_link($path): is a real link");
1646 119 100       318 return $self->parent_link_scheduled_for_removal($path) ? 0 : 1;
1647             }
1648              
1649 247         1195 debug(4, " is_a_link($path): returning 0");
1650 247         1768 return 0;
1651             }
1652              
1653             #===== METHOD ===============================================================
1654             # Name : is_a_dir()
1655             # Purpose : determine if the given path is a current or planned directory
1656             # Parameters: $path
1657             # Returns : Boolean
1658             # Throws : none
1659             # Comments : returns false if an existing directory is scheduled for removal
1660             # : and true if a non-existent directory is scheduled for creation
1661             # : we also need to be sure we are not just following a link
1662             #============================================================================
1663             sub is_a_dir {
1664 50     50 0 85 my $self = shift;
1665 50         83 my ($path) = @_;
1666 50         147 debug(4, " is_a_dir($path)");
1667              
1668 50 50       109 if (my $action = $self->dir_task_action($path)) {
1669 0 0       0 if ($action eq 'remove') {
    0          
1670 0         0 return 0;
1671             }
1672             elsif ($action eq 'create') {
1673 0         0 return 1;
1674             }
1675             }
1676              
1677 50 50       107 return 0 if $self->parent_link_scheduled_for_removal($path);
1678              
1679 50 100       643 if (-d $path) {
1680 43         217 debug(4, " is_a_dir($path): real dir");
1681 43         148 return 1;
1682             }
1683              
1684 7         41 debug(4, " is_a_dir($path): returning false");
1685 7         19 return 0;
1686             }
1687              
1688             #===== METHOD ===============================================================
1689             # Name : is_a_node()
1690             # Purpose : determine whether the given path is a current or planned node
1691             # Parameters: $path
1692             # Returns : Boolean
1693             # Throws : none
1694             # Comments : returns false if an existing node is scheduled for removal
1695             # : true if a non-existent node is scheduled for creation
1696             # : we also need to be sure we are not just following a link
1697             #============================================================================
1698             sub is_a_node {
1699 350     350 0 524 my $self = shift;
1700 350         619 my ($path) = @_;
1701 350         1120 debug(4, " is_a_node($path)");
1702              
1703 350         823 my $laction = $self->link_task_action($path);
1704 350         745 my $daction = $self->dir_task_action($path);
1705              
1706 350 100       868 if ($laction eq 'remove') {
    100          
1707 17 50       61 if ($daction eq 'remove') {
    100          
1708 0         0 internal_error("removing link and dir: $path");
1709 0         0 return 0;
1710             }
1711             elsif ($daction eq 'create') {
1712             # Assume that we're unfolding $path, and that the link
1713             # removal action is earlier than the dir creation action
1714             # in the task queue. FIXME: is this a safe assumption?
1715 6         17 return 1;
1716             }
1717             else { # no dir action
1718 11         41 return 0;
1719             }
1720             }
1721             elsif ($laction eq 'create') {
1722 2 50       6 if ($daction eq 'remove') {
    0          
1723             # Assume that we're folding $path, and that the dir
1724             # removal action is earlier than the link creation action
1725             # in the task queue. FIXME: is this a safe assumption?
1726 2         6 return 1;
1727             }
1728             elsif ($daction eq 'create') {
1729 0         0 internal_error("creating link and dir: $path");
1730 0         0 return 1;
1731             }
1732             else { # no dir action
1733 0         0 return 1;
1734             }
1735             }
1736             else {
1737             # No link action
1738 331 50       757 if ($daction eq 'remove') {
    100          
1739 0         0 return 0;
1740             }
1741             elsif ($daction eq 'create') {
1742 22         50 return 1;
1743             }
1744             else { # no dir action
1745             # fall through to below
1746             }
1747             }
1748              
1749 309 100       632 return 0 if $self->parent_link_scheduled_for_removal($path);
1750              
1751 301 100       3766 if (-e $path) {
1752 234         1152 debug(4, " is_a_node($path): really exists");
1753 234         701 return 1;
1754             }
1755              
1756 67         332 debug(4, " is_a_node($path): returning false");
1757 67         732 return 0;
1758             }
1759              
1760             #===== METHOD ===============================================================
1761             # Name : read_a_link()
1762             # Purpose : return the source of a current or planned link
1763             # Parameters: $path => path to the link target
1764             # Returns : a string
1765             # Throws : fatal exception if the given path is not a current or planned
1766             # : link
1767             # Comments : none
1768             #============================================================================
1769             sub read_a_link {
1770 125     125 0 175 my $self = shift;
1771 125         197 my ($path) = @_;
1772              
1773 125 100       229 if (my $action = $self->link_task_action($path)) {
    50          
1774 7         39 debug(4, " read_a_link($path): task exists with action $action");
1775              
1776 7 50       15 if ($action eq 'create') {
    0          
1777 7         31 return $self->{link_task_for}{$path}->{source};
1778             }
1779             elsif ($action eq 'remove') {
1780 0         0 internal_error(
1781             "read_a_link() passed a path that is scheduled for removal: $path"
1782             );
1783             }
1784             }
1785             elsif (-l $path) {
1786 118         652 debug(4, " read_a_link($path): real link");
1787 118 50       1032 my $target = readlink $path or error("Could not read link: $path ($!)");
1788 118         397 return $target;
1789             }
1790 0         0 internal_error("read_a_link() passed a non link path: $path\n");
1791             }
1792              
1793             #===== METHOD ===============================================================
1794             # Name : do_link()
1795             # Purpose : wrap 'link' operation for later processing
1796             # Parameters: $oldfile => the existing file to link to
1797             # : $newfile => the file to link
1798             # Returns : n/a
1799             # Throws : error if this clashes with an existing planned operation
1800             # Comments : cleans up operations that undo previous operations
1801             #============================================================================
1802             sub do_link {
1803 75     75 0 119 my $self = shift;
1804 75         151 my ($oldfile, $newfile) = @_;
1805              
1806 75 100       166 if (exists $self->{dir_task_for}{$newfile}) {
1807 7         17 my $task_ref = $self->{dir_task_for}{$newfile};
1808              
1809 7 50       43 if ($task_ref->{action} eq 'create') {
    50          
1810 0 0       0 if ($task_ref->{type} eq 'dir') {
1811 0         0 internal_error(
1812             "new link (%s => %s) clashes with planned new directory",
1813             $newfile,
1814             $oldfile,
1815             );
1816             }
1817             }
1818             elsif ($task_ref->{action} eq 'remove') {
1819             # We may need to remove a directory before creating a link so continue.
1820             }
1821             else {
1822 0         0 internal_error("bad task action: $task_ref->{action}");
1823             }
1824             }
1825              
1826 75 100       154 if (exists $self->{link_task_for}{$newfile}) {
1827 2         5 my $task_ref = $self->{link_task_for}{$newfile};
1828              
1829 2 50       9 if ($task_ref->{action} eq 'create') {
    50          
1830 0 0       0 if ($task_ref->{source} ne $oldfile) {
1831             internal_error(
1832             "new link clashes with planned new link: %s => %s",
1833             $task_ref->{path},
1834             $task_ref->{source},
1835             )
1836 0         0 }
1837             else {
1838 0         0 debug(1, "LINK: $newfile => $oldfile (duplicates previous action)");
1839 0         0 return;
1840             }
1841             }
1842             elsif ($task_ref->{action} eq 'remove') {
1843 2 50       7 if ($task_ref->{source} eq $oldfile) {
1844             # No need to remove a link we are going to recreate
1845 0         0 debug(1, "LINK: $newfile => $oldfile (reverts previous action)");
1846 0         0 $self->{link_task_for}{$newfile}->{action} = 'skip';
1847 0         0 delete $self->{link_task_for}{$newfile};
1848 0         0 return;
1849             }
1850             # We may need to remove a link to replace it so continue
1851             }
1852             else {
1853 0         0 internal_error("bad task action: $task_ref->{action}");
1854             }
1855             }
1856              
1857             # Creating a new link
1858 75         253 debug(1, "LINK: $newfile => $oldfile");
1859 75         330 my $task = {
1860             action => 'create',
1861             type => 'link',
1862             path => $newfile,
1863             source => $oldfile,
1864             };
1865 75         109 push @{ $self->{tasks} }, $task;
  75         184  
1866 75         191 $self->{link_task_for}{$newfile} = $task;
1867              
1868 75         134 return;
1869             }
1870              
1871             #===== METHOD ===============================================================
1872             # Name : do_unlink()
1873             # Purpose : wrap 'unlink' operation for later processing
1874             # Parameters: $file => the file to unlink
1875             # Returns : n/a
1876             # Throws : error if this clashes with an existing planned operation
1877             # Comments : will remove an existing planned link
1878             #============================================================================
1879             sub do_unlink {
1880 48     48 0 82 my $self = shift;
1881 48         95 my ($file) = @_;
1882              
1883 48 100       122 if (exists $self->{link_task_for}{$file}) {
1884 7         15 my $task_ref = $self->{link_task_for}{$file};
1885 7 50       26 if ($task_ref->{action} eq 'remove') {
    50          
1886 0         0 debug(1, "UNLINK: $file (duplicates previous action)");
1887 0         0 return;
1888             }
1889             elsif ($task_ref->{action} eq 'create') {
1890             # Do need to create a link then remove it
1891 7         25 debug(1, "UNLINK: $file (reverts previous action)");
1892 7         13 $self->{link_task_for}{$file}->{action} = 'skip';
1893 7         19 delete $self->{link_task_for}{$file};
1894 7         12 return;
1895             }
1896             else {
1897 0         0 internal_error("bad task action: $task_ref->{action}");
1898             }
1899             }
1900              
1901 41 50 33     103 if (exists $self->{dir_task_for}{$file} and $self->{dir_task_for}{$file} eq 'create') {
1902             internal_error(
1903             "new unlink operation clashes with planned operation: %s dir %s",
1904             $self->{dir_task_for}{$file}->{action},
1905 0         0 $file
1906             );
1907             }
1908              
1909             # Remove the link
1910 41         138 debug(1, "UNLINK: $file");
1911              
1912 41 50       426 my $source = readlink $file or error("could not readlink $file ($!)");
1913              
1914 41         240 my $task = {
1915             action => 'remove',
1916             type => 'link',
1917             path => $file,
1918             source => $source,
1919             };
1920 41         76 push @{ $self->{tasks} }, $task;
  41         121  
1921 41         117 $self->{link_task_for}{$file} = $task;
1922              
1923 41         98 return;
1924             }
1925              
1926             #===== METHOD ===============================================================
1927             # Name : do_mkdir()
1928             # Purpose : wrap 'mkdir' operation
1929             # Parameters: $dir => the directory to remove
1930             # Returns : n/a
1931             # Throws : fatal exception if operation fails
1932             # Comments : outputs a message if 'verbose' option is set
1933             # : does not perform operation if 'simulate' option is set
1934             # Comments : cleans up operations that undo previous operations
1935             #============================================================================
1936             sub do_mkdir {
1937 19     19 0 28 my $self = shift;
1938 19         38 my ($dir) = @_;
1939              
1940 19 100       43 if (exists $self->{link_task_for}{$dir}) {
1941 3         6 my $task_ref = $self->{link_task_for}{$dir};
1942              
1943 3 50       21 if ($task_ref->{action} eq 'create') {
    50          
1944             internal_error(
1945             "new dir clashes with planned new link (%s => %s)",
1946             $task_ref->{path},
1947             $task_ref->{source},
1948 0         0 );
1949             }
1950             elsif ($task_ref->{action} eq 'remove') {
1951             # May need to remove a link before creating a directory so continue
1952             }
1953             else {
1954 0         0 internal_error("bad task action: $task_ref->{action}");
1955             }
1956             }
1957              
1958 19 50       41 if (exists $self->{dir_task_for}{$dir}) {
1959 0         0 my $task_ref = $self->{dir_task_for}{$dir};
1960              
1961 0 0       0 if ($task_ref->{action} eq 'create') {
    0          
1962 0         0 debug(1, "MKDIR: $dir (duplicates previous action)");
1963 0         0 return;
1964             }
1965             elsif ($task_ref->{action} eq 'remove') {
1966 0         0 debug(1, "MKDIR: $dir (reverts previous action)");
1967 0         0 $self->{dir_task_for}{$dir}->{action} = 'skip';
1968 0         0 delete $self->{dir_task_for}{$dir};
1969 0         0 return;
1970             }
1971             else {
1972 0         0 internal_error("bad task action: $task_ref->{action}");
1973             }
1974             }
1975              
1976 19         65 debug(1, "MKDIR: $dir");
1977 19         76 my $task = {
1978             action => 'create',
1979             type => 'dir',
1980             path => $dir,
1981             source => undef,
1982             };
1983 19         26 push @{ $self->{tasks} }, $task;
  19         46  
1984 19         47 $self->{dir_task_for}{$dir} = $task;
1985              
1986 19         27 return;
1987             }
1988              
1989             #===== METHOD ===============================================================
1990             # Name : do_rmdir()
1991             # Purpose : wrap 'rmdir' operation
1992             # Parameters: $dir => the directory to remove
1993             # Returns : n/a
1994             # Throws : fatal exception if operation fails
1995             # Comments : outputs a message if 'verbose' option is set
1996             # : does not perform operation if 'simulate' option is set
1997             #============================================================================
1998             sub do_rmdir {
1999 7     7 0 25 my $self = shift;
2000 7         14 my ($dir) = @_;
2001              
2002 7 50       21 if (exists $self->{link_task_for}{$dir}) {
2003 0         0 my $task_ref = $self->{link_task_for}{$dir};
2004             internal_error(
2005             "rmdir clashes with planned operation: %s link %s => %s",
2006             $task_ref->{action},
2007             $task_ref->{path},
2008             $task_ref->{source}
2009 0         0 );
2010             }
2011              
2012 7 50       18 if (exists $self->{dir_task_for}{$dir}) {
2013 0         0 my $task_ref = $self->{link_task_for}{$dir};
2014              
2015 0 0       0 if ($task_ref->{action} eq 'remove') {
    0          
2016 0         0 debug(1, "RMDIR $dir (duplicates previous action)");
2017 0         0 return;
2018             }
2019             elsif ($task_ref->{action} eq 'create') {
2020 0         0 debug(1, "MKDIR $dir (reverts previous action)");
2021 0         0 $self->{link_task_for}{$dir}->{action} = 'skip';
2022 0         0 delete $self->{link_task_for}{$dir};
2023 0         0 return;
2024             }
2025             else {
2026 0         0 internal_error("bad task action: $task_ref->{action}");
2027             }
2028             }
2029              
2030 7         34 debug(1, "RMDIR $dir");
2031 7         40 my $task = {
2032             action => 'remove',
2033             type => 'dir',
2034             path => $dir,
2035             source => '',
2036             };
2037 7         12 push @{ $self->{tasks} }, $task;
  7         18  
2038 7         16 $self->{dir_task_for}{$dir} = $task;
2039              
2040 7         13 return;
2041             }
2042              
2043             #===== METHOD ===============================================================
2044             # Name : do_mv()
2045             # Purpose : wrap 'move' operation for later processing
2046             # Parameters: $src => the file to move
2047             # : $dst => the path to move it to
2048             # Returns : n/a
2049             # Throws : error if this clashes with an existing planned operation
2050             # Comments : alters contents of package installation image in stow dir
2051             #============================================================================
2052             sub do_mv {
2053 2     2 0 4 my $self = shift;
2054 2         5 my ($src, $dst) = @_;
2055              
2056 2 50       10 if (exists $self->{link_task_for}{$src}) {
    50          
2057             # I don't *think* this should ever happen, but I'm not
2058             # 100% sure.
2059 0         0 my $task_ref = $self->{link_task_for}{$src};
2060             internal_error(
2061             "do_mv: pre-existing link task for $src; action: %s, source: %s",
2062             $task_ref->{action}, $task_ref->{source}
2063 0         0 );
2064             }
2065             elsif (exists $self->{dir_task_for}{$src}) {
2066 0         0 my $task_ref = $self->{dir_task_for}{$src};
2067             internal_error(
2068             "do_mv: pre-existing dir task for %s?! action: %s",
2069             $src, $task_ref->{action}
2070 0         0 );
2071             }
2072              
2073             # Remove the link
2074 2         10 debug(1, "MV: $src -> $dst");
2075              
2076 2         12 my $task = {
2077             action => 'move',
2078             type => 'file',
2079             path => $src,
2080             dest => $dst,
2081             };
2082 2         4 push @{ $self->{tasks} }, $task;
  2         30  
2083              
2084             # FIXME: do we need this for anything?
2085             #$self->{mv_task_for}{$file} = $task;
2086              
2087 2         31 return;
2088             }
2089              
2090              
2091             #############################################################################
2092             #
2093             # End of methods; subroutines follow.
2094             # FIXME: Ideally these should be in a separate module.
2095              
2096              
2097             #===== PRIVATE SUBROUTINE ===================================================
2098             # Name : internal_error()
2099             # Purpose : output internal error message in a consistent form and die
2100             # Parameters: $message => error message to output
2101             # Returns : n/a
2102             # Throws : n/a
2103             # Comments : none
2104             #============================================================================
2105             sub internal_error {
2106 0     0 0   my ($format, @args) = @_;
2107 0           my $error = sprintf($format, @args);
2108 0           my $stacktrace = Carp::longmess();
2109 0           die <
2110              
2111             $ProgramName: INTERNAL ERROR: $error$stacktrace
2112              
2113             This _is_ a bug. Please submit a bug report so we can fix it! :-)
2114             See http://www.gnu.org/software/stow/ for how to do this.
2115             EOF
2116             }
2117              
2118             =head1 BUGS
2119              
2120             =head1 SEE ALSO
2121              
2122             =cut
2123              
2124             1;
2125              
2126             # Local variables:
2127             # mode: perl
2128             # cperl-indent-level: 4
2129             # end:
2130             # vim: ft=perl
2131              
2132             #############################################################################
2133             # Default global list of ignore regexps follows
2134             # (automatically appended by the Makefile)
2135              
2136             __DATA__