3 # GNU Stow - manage the installation of multiple software packages
4 # Copyright (C) 1993, 1994, 1995, 1996 by Bob Glickstein
5 # Copyright (C) 2000, 2001 Guillaume Morin
6 # Copyright (C) 2007 Kahlil Hodgson
8 # This program is free software; you can redistribute it and/or modify
9 # it under the terms of the GNU General Public License as published by
10 # the Free Software Foundation; either version 2 of the License, or
11 # (at your option) any later version.
13 # This program is distributed in the hope that it will be useful, but
14 # WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 # General Public License for more details.
18 # You should have received a copy of the GNU General Public License
19 # along with this program; if not, write to the Free Software
20 # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
29 my $Version = '2.0.2';
31 $ProgramName =~ s{.*/}{};
36 # > 0: print operations: LINK/UNLINK/MKDIR/RMDIR
37 # > 1: print trace: stow/unstow package/contents/node
38 # > 2: print trace detail: "_this_ already points to _that_"
40 # All output (except for version() and usage()) is to stderr to preserve
41 # backward compatibility.
43 # These are the defaults for command line options
58 # This becomes static after option processing
59 our $Stow_Path; # only use in main loop and find_stowed_path()
61 # Store conflicts during pre-processing
64 # Store command line packges to stow (-S and -R)
65 our @Pkgs_To_Stow = ();
67 # Store command line packages to unstow (-D and -R)
68 our @Pkgs_To_Delete = ();
70 # The following structures are used by the abstractions that allow us to
71 # defer operating on the filesystem until after all potential conflcits have
74 # our @Tasks: list of operations to be performed (in order)
75 # each element is a hash ref of the form
79 # path => ... (unique)
80 # source => ... (only for links)
84 # my %Dir_Task_For: map a path to the corresponding directory task reference
85 # This structurew allows us to quickly determine if a path has an existing
86 # directory task associated with it.
87 our %Dir_Task_For = ();
89 # my %Link_Task_For: map a path to the corresponding directory task reference
90 # This structurew allows us to quickly determine if a path has an existing
91 # directory task associated with it.
92 our %Link_Task_For = ();
94 # NB: directory tasks and link tasks are NOT mutually exclusive
96 # put the main loop in this block so we can load the
97 # rest of the code as a module for testing
103 # current dir is now the target directory
105 for my $package (@Pkgs_To_Delete) {
106 if (not -d join_paths
($Stow_Path,$package)) {
107 error
("The given package name ($package) is not in your stow path");
109 if ($Option{'verbose'} > 1) {
110 warn "Unstowing package $package...\n";
112 if ($Option{'compat'}) {
113 unstow_contents_orig
(
114 join_paths
($Stow_Path,$package), # path to package
115 '', # target is current_dir
120 join_paths
($Stow_Path,$package), # path to package
121 '', # target is current_dir
124 if ($Option{'verbose'} > 1) {
125 warn "Unstowing package $package...done\n";
129 for my $package (@Pkgs_To_Stow) {
130 if (not -d join_paths
($Stow_Path,$package)) {
131 error
("The given package name ($package) is not in your stow path");
133 if ($Option{'verbose'} > 1) {
134 warn "Stowing package $package...\n";
137 join_paths
($Stow_Path,$package), # path package
138 '', # target is current dir
139 join_paths
($Stow_Path,$package), # source from target
141 if ($Option{'verbose'} > 1) {
142 warn "Stowing package $package...done\n";
146 # --verbose: tell me what you are planning to do
147 # --simulate: don't execute planned operations
148 # --conflicts: just list any detected conflicts
150 if (scalar @Conflicts) {
151 warn "WARNING: conflicts detected.\n";
152 if ($Option{'conflicts'}) {
153 map { warn $_ } @Conflicts;
155 warn "WARNING: all operations aborted.\n";
163 #===== SUBROUTINE ===========================================================
164 # Name : process_options()
165 # Purpose : parse command line options and update the %Option hash
168 # Throws : a fatal error if a bad command line option is given
169 # Comments : checks @ARGV for valid package names
170 #============================================================================
171 sub process_options
{
174 #$,="\n"; print @ARGV,"\n"; # for debugging rc file
176 Getopt
::Long
::config
('no_ignore_case', 'bundling', 'permute');
178 'v' => sub { $Option{'verbose'}++ },
179 'verbose=s' => sub { $Option{'verbose'} = $_[1] },
180 'h|help' => sub { $Option{'help'} = '1' },
181 'n|no|simulate' => sub { $Option{'simulate'} = '1' },
182 'c|conflicts' => sub { $Option{'conflicts'} = '1' },
183 'V|version' => sub { $Option{'version'} = '1' },
184 'p|compat' => sub { $Option{'compat'} = '1' },
185 'd|dir=s' => sub { $Option{'dir'} = $_[1] },
186 't|target=s' => sub { $Option{'target'} = $_[1] },
188 # clean and pre-compile any regex's at parse time
191 my $regex = strip_quotes
($_[1]);
192 push @
{$Option{'ignore'}}, qr
($regex\z
)
197 my $regex = strip_quotes
($_[1]);
198 push @
{$Option{'override'}}, qr
(\A
$regex)
203 my $regex = strip_quotes
($_[1]);
204 push @
{$Option{'defer'}}, qr
(\A
$regex) ;
207 # a little craziness so we can do different actions on the same line:
208 # a -D, -S, or -R changes the action that will be performed on the
209 # package arguments that follow it.
210 'D|delete' => sub { $Option{'action'} = 'delete' },
211 'S|stow' => sub { $Option{'action'} = 'stow' },
212 'R|restow' => sub { $Option{'action'} = 'restow' },
215 if ($Option{'action'} eq 'restow') {
216 push @Pkgs_To_Delete, $_[0];
217 push @Pkgs_To_Stow, $_[0];
219 elsif ($Option{'action'} eq 'delete') {
220 push @Pkgs_To_Delete, $_[0];
223 push @Pkgs_To_Stow, $_[0];
228 #print "$Option{'dir'}\n"; print "$Option{'target'}\n"; exit;
230 # clean any leading and trailing whitespace in paths
231 if ($Option{'dir'}) {
232 $Option{'dir'} =~ s/\A +//;
233 $Option{'dir'} =~ s/ +\z//;
235 if ($Option{'target'}) {
236 $Option{'target'} =~ s/\A +//;
237 $Option{'target'} =~ s/ +\z//;
240 if ($Option{'help'}) {
243 if ($Option{'version'}) {
246 if ($Option{'conflicts'}) {
247 $Option{'simulate'} = 1;
250 if (not scalar @Pkgs_To_Stow and not scalar @Pkgs_To_Delete ) {
251 usage
("No packages named");
254 # check package arguments
255 for my $package ( (@Pkgs_To_Stow, @Pkgs_To_Delete) ) {
256 $package =~ s{/+$}{}; # delete trailing slashes
257 if ( $package =~ m{/} ) {
258 error
("Slashes are not permitted in package names");
265 #===== SUBROUTINE ============================================================
266 # Name : get_defaults()
267 # Purpose : search for default settings in any .stow files
270 # Throws : no exceptions
271 # Comments : prepends the contents '~/.stowrc' and '.stowrc' to the command
272 # : line so they get parsed just like noremal arguments. (This was
273 # : hacked in so that Emil and I could set different preferences).
274 #=============================================================================
278 for my $file ($ENV{'HOME'}.'/.stowrc','.stowrc') {
280 warn "Loading defaults from $file\n";
281 open my $FILE, '<', $file
282 or die "Could not open $file for reading\n";
283 while (my $line = <$FILE> ){
285 push @defaults, split " ", $line;
287 close $FILE or die "Could not close open file: $file\n";
290 # doing this inline does not seem to work
291 unshift @ARGV, @defaults;
295 #===== SUBROUTINE ===========================================================
297 # Purpose : print program usage message and exit
298 # Parameters: msg => string to prepend to the usage message
301 # Comments : if 'msg' is given, then exit with non-zero status
302 #============================================================================
307 print "$ProgramName: $msg\n\n";
311 $ProgramName (GNU Stow) version $Version
315 $ProgramName [OPTION ...] [-D|-S|-R] PACKAGE ... [-D|-S|-R] PACKAGE ...
319 -n, --no Do not actually make any filesystem changes
320 -c, --conflicts Scan for and print any conflicts, implies -n
321 -d DIR, --dir=DIR Set stow dir to DIR (default is current dir)
322 -t DIR, --target=DIR Set target to DIR (default is parent of stow dir)
323 -v, --verbose[=N] Increase verbosity (levels are 0,1,2,3;
324 -v or --verbose adds 1; --verbose=N sets level)
326 -S, --stow Stow the package names that follow this option
327 -D, --delete Unstow the package names that follow this option
328 -R, --restow Restow (like stow -D followed by stow -S)
329 -p, --compat use legacy algorithm for unstowing
331 --ignore=REGEX ignore files ending in this perl regex
332 --defer=REGEX defer stowing files begining with this perl regex
333 if the file is already stowed to another package
334 --override=REGEX force stowing files begining with this perl regex
335 if the file is already stowed to another package
336 -V, --version Show stow version number
337 -h, --help Show this help
339 exit( $msg ?
1 : 0 );
342 #===== SUBROUTINE ===========================================================
343 # Name : set_stow_path()
344 # Purpose : find the relative path to the stow directory
346 # Returns : a relative path
347 # Throws : fatal error if either default directories or those set by the
348 # : the command line flags are not valid.
349 # Comments : This sets the current working directory to $Option{target}
350 #============================================================================
353 # Changing dirs helps a lot when soft links are used
354 # Also prevents problems when 'stow_dir' or 'target' are
355 # supplied as relative paths (FIXME: examples?)
357 my $current_dir = getcwd
();
359 # default stow dir is the current directory
360 if (not $Option{'dir'} ) {
361 $Option{'dir'} = getcwd
();
363 if (not chdir($Option{'dir'})) {
364 error
("Cannot chdir to target tree: '$Option{'dir'}'");
366 my $stow_dir = getcwd
();
368 # back to start in case target is relative
369 if (not chdir($current_dir)) {
370 error
("Your directory does not seem to exist anymore");
373 # default target is the parent of the stow directory
374 if (not $Option{'target'}) {
375 $Option{'target'} = parent
($Option{'dir'});
377 if (not chdir($Option{'target'})) {
378 error
("Cannot chdir to target tree: $Option{'target'}");
382 $Stow_Path = relative_path
(getcwd
(),$stow_dir);
384 if ($Option{'verbose'} > 1) {
385 warn "current dir is ".getcwd
()."\n";
386 warn "stow dir path is $Stow_Path\n";
390 #===== SUBROUTINE ===========================================================
391 # Name : stow_contents()
392 # Purpose : stow the contents of the given directory
393 # Parameters: $path => relative path to source dir from current directory
394 # : $source => relative path to symlink source from the dir of target
395 # : $target => relative path to symlink target from the current directory
397 # Throws : a fatal error if directory cannot be read
398 # Comments : stow_node() and stow_contents() are mutually recursive
399 # : $source and $target are used for creating the symlink
400 # : $path is used for folding/unfolding trees as necessary
401 #============================================================================
404 my ($path, $target, $source) = @_;
406 if ($Option{'verbose'} > 1){
407 warn "Stowing contents of $path\n";
409 if ($Option{'verbose'} > 2){
410 warn "--- $target => $source\n";
414 error
("stow_contents() called on a non-directory: $path");
417 opendir my $DIR, $path
418 or error
("cannot read directory: $path");
419 my @listing = readdir $DIR;
423 for my $node (@listing) {
424 next NODE
if $node eq '.';
425 next NODE
if $node eq '..';
426 next NODE
if ignore
($node);
428 join_paths
($path, $node), # path
429 join_paths
($target,$node), # target
430 join_paths
($source,$node), # source
435 #===== SUBROUTINE ===========================================================
437 # Purpose : stow the given node
438 # Parameters: $path => realtive path to source node from the current directory
439 # : $target => realtive path to symlink target from the current directory
440 # : $source => realtive path to symlink source from the dir of target
442 # Throws : fatal exception if a conflict arises
443 # Comments : stow_node() and stow_contents() are mutually recursive
444 # : $source and $target are used for creating the symlink
445 # : $path is used for folding/unfolding trees as necessary
446 #============================================================================
449 my ($path, $target, $source) = @_;
451 if ($Option{'verbose'} > 1) {
452 warn "Stowing $path\n";
454 if ($Option{'verbose'} > 2) {
455 warn "--- $target => $source\n";
458 # don't try to stow absolute symlinks (they cant be unstowed)
460 my $second_source = read_a_link
($source);
461 if ($second_source =~ m{\A/} ) {
462 conflict
("source is an absolute symlink $source => $second_source");
463 if ($Option{'verbose'} > 2) {
464 warn "absolute symlinks cannot be unstowed";
470 # does the target already exist?
471 if (is_a_link
($target)) {
473 # where is the link pointing?
474 my $old_source = read_a_link
($target);
475 if (not $old_source) {
476 error
("Could not read link: $target");
478 if ($Option{'verbose'} > 2) {
479 warn "--- Evaluate existing link: $target => $old_source\n";
482 # does it point to a node under our stow directory?
483 my $old_path = find_stowed_path
($target, $old_source);
485 conflict
("existing target is not owned by stow: $target");
489 # does the existing $target actually point to anything?
490 if (is_a_node
($old_path)) {
491 if ($old_source eq $source) {
492 if ($Option{'verbose'} > 2) {
493 warn "--- Skipping $target as it already points to $source\n";
496 elsif (defer
($target)) {
497 if ($Option{'verbose'} > 2) {
498 warn "--- deferring installation of: $target\n";
501 elsif (override
($target)) {
502 if ($Option{'verbose'} > 2) {
503 warn "--- overriding installation of: $target\n";
506 do_link
($source,$target);
508 elsif (is_a_dir
(join_paths
(parent
($target),$old_source)) &&
509 is_a_dir
(join_paths
(parent
($target),$source)) ) {
511 # if the existing link points to a directory,
512 # and the proposed new link points to a directory,
513 # then we can unfold the tree at that point
515 if ($Option{'verbose'} > 2){
516 warn "--- Unfolding $target\n";
520 stow_contents
($old_path, $target, join_paths
('..',$old_source));
521 stow_contents
($path, $target, join_paths
('..',$source));
525 q{existing target is stowed to a different package: %s => %s},
532 # the existing link is invalid, so replace it with a good link
533 if ($Option{'verbose'} > 2){
534 warn "--- replacing invalid link: $path\n";
537 do_link($source, $target);
540 elsif (is_a_node($target)) {
541 if ($Option{'verbose'} > 2) {
542 warn("--- Evaluate existing node: $target\n");
544 if (is_a_dir($target)) {
545 stow_contents($path, $target, join_paths('..',$source));
549 qq{existing target is neither a link nor a directory: $target}
554 do_link($source, $target);
559 #===== SUBROUTINE ===========================================================
560 # Name : unstow_contents_orig()
561 # Purpose : unstow the contents of the given directory
562 # Parameters: $path => relative path to source dir from current directory
563 # : $target => relative path to symlink target from the current directory
565 # Throws : a fatal error if directory cannot be read
566 # Comments : unstow_node() and unstow_contents() are mutually recursive
567 # : Here we traverse the target tree, rather than the source tree.
568 #============================================================================
569 sub unstow_contents_orig {
571 my ($path, $target) = @_;
573 # don't try to remove anything under a stow directory
574 if ($target eq $Stow_Path or -e "$target/.stow" or -e "$target/.nonstow") {
577 if ($Option{'verbose'} > 1){
578 warn "Unstowing in $target\n";
580 if ($Option{'verbose'} > 2){
581 warn "--- path is $path\n";
583 if (not -d $target) {
584 error("unstow_contents() called on a non-directory: $target");
587 opendir my $DIR, $target
588 or error("cannot read directory: $target");
589 my @listing = readdir $DIR;
593 for my $node (@listing) {
594 next NODE if $node eq '.';
595 next NODE if $node eq '..';
596 next NODE if ignore($node);
598 join_paths($path, $node), # path
599 join_paths($target, $node), # target
604 #===== SUBROUTINE ===========================================================
605 # Name : unstow_node_orig()
606 # Purpose : unstow the given node
607 # Parameters: $path => relative path to source node from the current directory
608 # : $target => relative path to symlink target from the current directory
610 # Throws : fatal error if a conflict arises
611 # Comments : unstow_node() and unstow_contents() are mutually recursive
612 #============================================================================
613 sub unstow_node_orig {
615 my ($path, $target) = @_;
617 if ($Option{'verbose'} > 1) {
618 warn "Unstowing $target\n";
620 if ($Option{'verbose'} > 2) {
621 warn "--- path is $path\n";
624 # does the target exist
625 if (is_a_link($target)) {
626 if ($Option{'verbose'} > 2) {
627 warn("Evaluate existing link: $target\n");
630 # where is the link pointing?
631 my $old_source = read_a_link($target);
632 if (not $old_source) {
633 error("Could not read link: $target");
636 # does it point to a node under our stow directory?
637 my $old_path = find_stowed_path($target, $old_source);
639 # skip links not owned by stow
643 # does the existing $target actually point to anything
645 # does link points to the right place
646 if ($old_path eq $path) {
649 elsif (override($target)) {
650 if ($Option{'verbose'} > 2) {
651 warn("--- overriding installation of: $target\n");
655 # else leave it alone
658 if ($Option{'verbose'} > 2){
659 warn "--- removing invalid link into a stow directory: $path\n";
665 unstow_contents_orig($path, $target);
667 # this action may have made the parent directory foldable
668 if (my $parent = foldable($target)) {
669 fold_tree($target,$parent);
675 #===== SUBROUTINE ===========================================================
676 # Name : unstow_contents()
677 # Purpose : unstow the contents of the given directory
678 # Parameters: $path => relative path to source dir from current directory
679 # : $target => relative path to symlink target from the current directory
681 # Throws : a fatal error if directory cannot be read
682 # Comments : unstow_node() and unstow_contents() are mutually recursive
683 # : Here we traverse the target tree, rather than the source tree.
684 #============================================================================
685 sub unstow_contents {
687 my ($path, $target) = @_;
689 # don't try to remove anything under a stow directory
690 if ($target eq $Stow_Path or -e "$target/.stow") {
693 if ($Option{'verbose'} > 1){
694 warn "Unstowing in $target\n";
696 if ($Option{'verbose'} > 2){
697 warn "--- path is $path\n";
700 error("unstow_contents() called on a non-directory: $path");
703 opendir my $DIR, $path
704 or error("cannot read directory: $path");
705 my @listing = readdir $DIR;
709 for my $node (@listing) {
710 next NODE if $node eq '.';
711 next NODE if $node eq '..';
712 next NODE if ignore($node);
714 join_paths($path, $node), # path
715 join_paths($target, $node), # target
719 cleanup_invalid_links($target);
723 #===== SUBROUTINE ===========================================================
724 # Name : unstow_node()
725 # Purpose : unstow the given node
726 # Parameters: $path => relative path to source node from the current directory
727 # : $target => relative path to symlink target from the current directory
729 # Throws : fatal error if a conflict arises
730 # Comments : unstow_node() and unstow_contents() are mutually recursive
731 #============================================================================
734 my ($path, $target) = @_;
736 if ($Option{'verbose'} > 1) {
737 warn "Unstowing $path\n";
739 if ($Option{'verbose'} > 2) {
740 warn "--- target is $target\n";
743 # does the target exist
744 if (is_a_link($target)) {
745 if ($Option{'verbose'} > 2) {
746 warn("Evaluate existing link: $target\n");
749 # where is the link pointing?
750 my $old_source = read_a_link($target);
751 if (not $old_source) {
752 error("Could not read link: $target");
755 if ($old_source =~ m{\A/}) {
756 warn "ignoring a absolute symlink: $target => $old_source\n";
760 # does it point to a node under our stow directory?
761 my $old_path = find_stowed_path($target, $old_source);
764 qq{existing target is not owned by stow: $target => $old_source}
769 # does the existing $target actually point to anything
771 # does link points to the right place
772 if ($old_path eq $path) {
776 # XXX we quietly ignore links that are stowed to a different
779 #elsif (defer($target)) {
780 # if ($Option{'verbose'} > 2) {
781 # warn("--- deferring to installation of: $target\n");
784 #elsif (override($target)) {
785 # if ($Option{'verbose'} > 2) {
786 # warn("--- overriding installation of: $target\n");
788 # do_unlink($target);
792 # q{existing target is stowed to a different package: %s => %s},
799 if ($Option{'verbose'} > 2){
800 warn "--- removing invalid link into a stow directory: $path\n";
806 if ($Option{'verbose'} > 2) {
807 warn("Evaluate existing node: $target\n");
810 unstow_contents($path, $target);
812 # this action may have made the parent directory foldable
813 if (my $parent = foldable($target)) {
814 fold_tree($target,$parent);
819 qq{existing target is neither a link nor a directory: $target},
826 #===== SUBROUTINE ===========================================================
827 # Name : find_stowed_path()
828 # Purpose : determine if the given link points to a member of a
830 # Parameters: $target => path to a symbolic link under current directory
831 # : $source => where that link points to
832 # Returns : relative path to stowed node (from the current directory)
833 # : or '' if link is not owned by stow
834 # Throws : fatal exception if link is unreadable
835 # Comments : allow for stow dir not being under target dir
836 # : we could put more logic under here for multiple stow dirs
837 #============================================================================
838 sub find_stowed_path {
840 my ($target, $source) = @_;
842 # evaluate softlink relative to its target
843 my $path = join_paths(parent($target), $source);
845 # search for .stow files
847 for my $part (split m{/+}, $path) {
848 $dir = join_paths($dir,$part);
849 if (-f "$dir/.stow") {
854 # compare with $Stow_Path
855 my @path = split m{/+}, $path;
856 my @stow_path = split m{/+}, $Stow_Path;
858 # strip off common prefixes
859 while ( @path && @stow_path ) {
860 if ( (shift @path) ne (shift @stow_path) ) {
865 # @path is not under @stow_dir
872 #===== SUBROUTINE ============================================================
873 # Name : cleanup_invalid_links()
874 # Purpose : clean up invalid links that may block folding
875 # Parameters: $dir => path to directory to check
877 # Throws : no exceptions
878 # Comments : removing files from a stowed package is probably a bad practice
879 # : so this kind of clean up is not _really_ stow's responsibility;
880 # : however, failing to clean up can block tree folding, so we'll do
882 #=============================================================================
883 sub cleanup_invalid_links {
888 error("cleanup_invalid_links() called with a non-directory: $dir");
891 opendir my $DIR, $dir
892 or error("cannot read directory: $dir");
893 my @listing = readdir $DIR;
897 for my $node (@listing) {
898 next NODE if $node eq '.';
899 next NODE if $node eq '..';
901 my $node_path = join_paths($dir,$node);
903 if (-l $node_path and not exists $Link_Task_For{$node_path}) {
905 # where is the link pointing?
906 # (dont use read_a_link here)
907 my $source = readlink($node_path);
909 error("Could not read link $node_path");
913 not -e join_paths($dir,$source) and # bad link
914 find_stowed_path($node_path,$source) # owned by stow
916 if ($Option{'verbose'} > 2) {
917 warn "--- removing stale link: $node_path => ",
918 join_paths($dir,$source), "\n";
920 do_unlink($node_path);
928 #===== SUBROUTINE ===========================================================
930 # Purpose : determine if a tree can be folded
931 # Parameters: target => path to a directory
932 # Returns : path to the parent dir iff the tree can be safely folded
934 # Comments : the path returned is relative to the parent of $target,
935 # : that is, it can be used as the source for a replacement symlink
936 #============================================================================
941 if ($Option{'verbose'} > 2){
942 warn "--- Is $target foldable?\n";
945 opendir my $DIR, $target
946 or error(qq{Cannot read directory "$target" ($!)\n});
947 my @listing = readdir $DIR;
952 for my $node (@listing) {
954 next NODE if $node eq '.';
955 next NODE if $node eq '..';
957 my $path = join_paths($target,$node);
959 # skip nodes scheduled for removal
960 next NODE if not is_a_node($path);
962 # if its not a link then we can't fold its parent
963 return '' if not is_a_link($path);
965 # where is the link pointing?
966 my $source = read_a_link($path);
968 error("Could not read link $path");
971 $parent = parent($source)
973 elsif ($parent ne parent($source)) {
977 return '' if not $parent;
979 # if we get here then all nodes inside $target are links, and those links
980 # point to nodes inside the same directory.
982 # chop of leading '..' to get the path to the common parent directory
983 # relative to the parent of our $target
984 $parent =~ s{\A\.\./}{};
986 # if the resulting path is owned by stow, we can fold it
987 if (find_stowed_path($target,$parent)) {
988 if ($Option{'verbose'} > 2){
989 warn "--- $target is foldable\n";
998 #===== SUBROUTINE ===========================================================
1000 # Purpose : fold the given tree
1001 # Parameters: $source => link to the folded tree source
1002 # : $target => directory that we will replace with a link to $source
1005 # Comments : only called iff foldable() is true so we can remove some checks
1006 #============================================================================
1009 my ($target,$source) = @_;
1011 if ($Option{'verbose'} > 2){
1012 warn "--- Folding tree: $target => $source\n";
1015 opendir my $DIR, $target
1016 or error(qq{Cannot read directory "$target" ($!)\n});
1017 my @listing = readdir $DIR;
1021 for my $node (@listing) {
1022 next NODE if $node eq '.';
1023 next NODE if $node eq '..';
1024 next NODE if not is_a_node(join_paths($target,$node));
1025 do_unlink(join_paths($target,$node));
1028 do_link($source, $target);
1033 #===== SUBROUTINE ===========================================================
1035 # Purpose : handle conflicts in stow operations
1036 # Parameters: paths that conflict
1038 # Throws : fatal exception unless 'conflicts' option is set
1039 # Comments : indicates what type of conflict it is
1040 #============================================================================
1042 my ( $format, @args ) = @_;
1044 my $message = sprintf($format, @args);
1046 if ($Option{'verbose'}) {
1047 warn qq{CONFLICT: $message\n};
1049 push @Conflicts, qq{CONFLICT: $message\n};
1053 #===== SUBROUTINE ============================================================
1055 # Purpose : determine if the given path matches a regex in our ignore list
1058 # Throws : no exceptions
1060 #=============================================================================
1065 for my $suffix (@{$Option{'ignore'}}) {
1066 return 1 if $path =~ m/$suffix/;
1071 #===== SUBROUTINE ============================================================
1073 # Purpose : determine if the given path matches a regex in our defer list
1076 # Throws : no exceptions
1078 #=============================================================================
1083 for my $prefix (@{$Option{'defer'}}) {
1084 return 1 if $path =~ m/$prefix/;
1089 #===== SUBROUTINE ============================================================
1091 # Purpose : determine if the given path matches a regex in our override list
1094 # Throws : no exceptions
1096 #=============================================================================
1101 for my $regex (@{$Option{'override'}}) {
1102 return 1 if $path =~ m/$regex/;
1107 ##############################################################################
1109 # The following code provides the abstractions that allow us to defer operating
1110 # on the filesystem until after all potential conflcits have been assessed.
1112 ##############################################################################
1114 #===== SUBROUTINE ===========================================================
1115 # Name : process_tasks()
1116 # Purpose : process each task in the @Tasks list
1119 # Throws : fatal error if @Tasks is corrupted or a task fails
1120 # Comments : task involve either creating or deleting dirs and symlinks
1121 # : an action is set to 'skip' if it is found to be redundant
1122 #============================================================================
1125 if ($Option{'verbose'} > 1) {
1126 warn "Processing tasks...\n"
1129 # strip out all tasks with a skip action
1130 @Tasks = grep { $_->{'action'} ne 'skip' } @Tasks;
1132 if (not scalar @Tasks) {
1133 warn "There are no outstanding operations to perform.\n";
1136 if ($Option{'simulate'}) {
1137 warn "WARNING: simulating so all operations are deferred.\n";
1141 for my $task (@Tasks) {
1143 if ( $task->{'action'} eq 'create' ) {
1144 if ( $task->{'type'} eq 'dir' ) {
1145 mkdir($task->{'path'}, 0777)
1146 or error(qq(Could not create directory: $task->{'path'}));
1148 elsif ( $task->{'type'} eq 'link' ) {
1149 symlink $task->{'source'}, $task->{'path'}
1151 q(Could not create symlink: %s => %s),
1157 internal_error(qq(bad task type: $task->{'type'}));
1160 elsif ( $task->{'action'} eq 'remove' ) {
1161 if ( $task->{'type'} eq 'dir' ) {
1162 rmdir $task->{'path'}
1163 or error(qq(Could not remove directory: $task->{'path'}));
1165 elsif ( $task->{'type'} eq 'link' ) {
1166 unlink $task->{'path'}
1167 or error(qq(Could not remove link: $task->{'path'}));
1170 internal_error(qq(bad task type: $task->{'type'}));
1174 internal_error(qq(bad task action: $task->{'action'}));
1177 if ($Option{'verbose'} > 1) {
1178 warn "Processing tasks...done\n"
1183 #===== SUBROUTINE ===========================================================
1184 # Name : is_a_link()
1185 # Purpose : is the given path a current or planned link
1189 # Comments : returns false if an existing link is scheduled for removal
1190 # : and true if a non-exsitent link is scheduled for creation
1191 #============================================================================
1196 if ( exists $Link_Task_For{$path} ) {
1198 my $action = $Link_Task_For{$path}->{'action'};
1200 if ($action eq 'remove') {
1203 elsif ($action eq 'create') {
1207 internal_error("bad task action: $action");
1211 # check if any of its parent are links scheduled for removal
1212 # (need this for edge case during unfolding)
1214 for my $part (split m{/+}, $path ) {
1215 $parent = join_paths($parent,$part);
1216 if ( exists $Link_Task_For{$parent} ) {
1217 if ($Link_Task_For{$parent}->{'action'} eq 'remove') {
1228 #===== SUBROUTINE ===========================================================
1230 # Purpose : is the given path a current or planned directory
1234 # Comments : returns false if an existing directory is scheduled for removal
1235 # : and true if a non-existent directory is scheduled for creation
1236 # : we also need to be sure we are not just following a link
1237 #============================================================================
1241 if ( exists $Dir_Task_For{$path} ) {
1242 my $action = $Dir_Task_For{$path}->{'action'};
1243 if ($action eq 'remove') {
1246 elsif ($action eq 'create') {
1250 internal_error("bad task action: $action");
1254 # are we really following a link that is scheduled for removal
1256 for my $part (split m{/+}, $path) {
1257 $prefix = join_paths($prefix,$part);
1258 if (exists $Link_Task_For{$prefix} and
1259 $Link_Task_For{$prefix}->{'action'} eq 'remove') {
1270 #===== SUBROUTINE ===========================================================
1271 # Name : is_a_node()
1272 # Purpose : is the given path a current or planned node
1276 # Comments : returns false if an existing node is scheduled for removal
1277 # : true if a non-existent node is scheduled for creation
1278 # : we also need to be sure we are not just following a link
1279 #============================================================================
1283 if ( exists $Link_Task_For{$path} ) {
1285 my $action = $Link_Task_For{$path}->{'action'};
1287 if ($action eq 'remove') {
1290 elsif ($action eq 'create') {
1294 internal_error("bad task action: $action");
1298 if ( exists $Dir_Task_For{$path} ) {
1300 my $action = $Dir_Task_For{$path}->{'action'};
1302 if ($action eq 'remove') {
1305 elsif ($action eq 'create') {
1309 internal_error("bad task action: $action");
1313 # are we really following a link that is scheduled for removal
1315 for my $part (split m{/+}, $path) {
1316 $prefix = join_paths($prefix,$part);
1317 if ( exists $Link_Task_For{$prefix} and
1318 $Link_Task_For{$prefix}->{'action'} eq 'remove') {
1329 #===== SUBROUTINE ===========================================================
1330 # Name : read_a_link()
1331 # Purpose : return the source of a current or planned link
1332 # Parameters: $path => path to the link target
1333 # Returns : a string
1334 # Throws : fatal exception if the given path is not a current or planned
1337 #============================================================================
1342 if ( exists $Link_Task_For{$path} ) {
1343 my $action = $Link_Task_For{$path}->{'action'};
1345 if ($action eq 'create') {
1346 return $Link_Task_For{$path}->{'source'};
1348 elsif ($action eq 'remove') {
1350 "read_a_link() passed a path that is scheduled for removal: $path"
1354 internal_error("bad task action: $action");
1358 return readlink $path
1359 or error("Could not read link: $path");
1361 internal_error("read_a_link() passed a non link path: $path\n");
1364 #===== SUBROUTINE ===========================================================
1366 # Purpose : wrap 'link' operation for later processing
1367 # Parameters: file => the file to link
1369 # Throws : error if this clashes with an existing planned operation
1370 # Comments : cleans up operations that undo previous operations
1371 #============================================================================
1374 my ( $oldfile, $newfile ) = @_;
1376 if ( exists $Dir_Task_For{$newfile} ) {
1378 my $task_ref = $Dir_Task_For{$newfile};
1380 if ( $task_ref->{'action'} eq 'create' ) {
1381 if ($task_ref->{'type'} eq 'dir') {
1383 "new link (%s => %s ) clashes with planned new directory",
1389 elsif ( $task_ref->{'action'} eq 'remove' ) {
1390 # we may need to remove a directory before creating a link so continue;
1393 internal_error("bad task action: $task_ref->{'action'}");
1397 if ( exists $Link_Task_For{$newfile} ) {
1399 my $task_ref = $Link_Task_For{$newfile};
1401 if ( $task_ref->{'action'} eq 'create' ) {
1402 if ( $task_ref->{'source'} ne $oldfile ) {
1404 "new link clashes with planned new link: %s => %s",
1405 $task_ref->{'path'},
1406 $task_ref->{'source'},
1410 if ($Option{'verbose'}) {
1411 warn "LINK: $newfile => $oldfile (duplicates previous action)\n";
1416 elsif ( $task_ref->{'action'} eq 'remove' ) {
1417 if ( $task_ref->{'source'} eq $oldfile ) {
1418 # no need to remove a link we are going to recreate
1419 if ($Option{'verbose'}) {
1420 warn "LINK: $newfile => $oldfile (reverts previous action)\n";
1422 $Link_Task_For{$newfile}->{'action'} = 'skip';
1423 delete $Link_Task_For{$newfile};
1426 # we may need to remove a link to replace it so continue
1429 internal_error("bad task action: $task_ref->{'action'}");
1433 # creating a new link
1434 if ($Option{'verbose'}) {
1435 warn "LINK: $newfile => $oldfile\n";
1444 $Link_Task_For{$newfile} = $task;
1449 #===== SUBROUTINE ===========================================================
1450 # Name : do_unlink()
1451 # Purpose : wrap 'unlink' operation for later processing
1452 # Parameters: $file => the file to unlink
1454 # Throws : error if this clashes with an existing planned operation
1455 # Comments : will remove an existing planned link
1456 #============================================================================
1461 if (exists $Link_Task_For{$file} ) {
1462 my $task_ref = $Link_Task_For{$file};
1463 if ( $task_ref->{'action'} eq 'remove' ) {
1464 if ($Option{'verbose'}) {
1465 warn "UNLINK: $file (duplicates previous action)\n";
1469 elsif ( $task_ref->{'action'} eq 'create' ) {
1470 # do need to create a link then remove it
1471 if ($Option{'verbose'}) {
1472 warn "UNLINK: $file (reverts previous action)\n";
1474 $Link_Task_For{$file}->{'action'} = 'skip';
1475 delete $Link_Task_For{$file};
1479 internal_error("bad task action: $task_ref->{'action'}");
1483 if ( exists $Dir_Task_For{$file} and $Dir_Task_For{$file} eq 'create' ) {
1485 "new unlink operation clashes with planned operation: %s dir %s",
1486 $Dir_Task_For{$file}->{'action'},
1492 if ($Option{'verbose'}) {
1493 #warn "UNLINK: $file (".(caller())[2].")\n";
1494 warn "UNLINK: $file\n";
1497 my $source = readlink $file or error("could not readlink $file");
1506 $Link_Task_For{$file} = $task;
1511 #===== SUBROUTINE ===========================================================
1513 # Purpose : wrap 'mkdir' operation
1514 # Parameters: $dir => the directory to remove
1516 # Throws : fatal exception if operation fails
1517 # Comments : outputs a message if 'verbose' option is set
1518 # : does not perform operation if 'simulate' option is set
1519 # Comments : cleans up operations that undo previous operations
1520 #============================================================================
1524 if ( exists $Link_Task_For{$dir} ) {
1526 my $task_ref = $Link_Task_For{$dir};
1528 if ($task_ref->{'action'} eq 'create') {
1530 "new dir clashes with planned new link (%s => %s)",
1531 $task_ref->{'path'},
1532 $task_ref->{'source'},
1535 elsif ($task_ref->{'action'} eq 'remove') {
1536 # may need to remove a link before creating a directory so continue
1539 internal_error("bad task action: $task_ref->{'action'}");
1543 if ( exists $Dir_Task_For{$dir} ) {
1545 my $task_ref = $Dir_Task_For{$dir};
1547 if ($task_ref->{'action'} eq 'create') {
1548 if ($Option{'verbose'}) {
1549 warn "MKDIR: $dir (duplicates previous action)\n";
1553 elsif ($task_ref->{'action'} eq 'remove') {
1554 if ($Option{'verbose'}) {
1555 warn "MKDIR: $dir (reverts previous action)\n";
1557 $Dir_Task_For{$dir}->{'action'} = 'skip';
1558 delete $Dir_Task_For{$dir};
1562 internal_error("bad task action: $task_ref->{'action'}");
1566 if ($Option{'verbose'}) {
1567 warn "MKDIR: $dir\n";
1576 $Dir_Task_For{$dir} = $task;
1581 #===== SUBROUTINE ===========================================================
1583 # Purpose : wrap 'rmdir' operation
1584 # Parameters: $dir => the directory to remove
1586 # Throws : fatal exception if operation fails
1587 # Comments : outputs a message if 'verbose' option is set
1588 # : does not perform operation if 'simulate' option is set
1589 #============================================================================
1593 if (exists $Link_Task_For{$dir} ) {
1594 my $task_ref = $Link_Task_For{$dir};
1596 "rmdir clashes with planned operation: %s link %s => %s",
1597 $task_ref->{'action'},
1598 $task_ref->{'path'},
1599 $task_ref->{'source'}
1603 if (exists $Dir_Task_For{$dir} ) {
1604 my $task_ref = $Link_Task_For{$dir};
1606 if ($task_ref->{'action'} eq 'remove' ) {
1607 if ($Option{'verbose'}) {
1608 warn "RMDIR $dir (duplicates previous action)\n";
1612 elsif ($task_ref->{'action'} eq 'create' ) {
1613 if ($Option{'verbose'}) {
1614 warn "MKDIR $dir (reverts previous action)\n";
1616 $Link_Task_For{$dir}->{'action'} = 'skip';
1617 delete $Link_Task_For{$dir};
1621 internal_error("bad task action: $task_ref->{'action'}");
1625 if ($Option{'verbose'}) {
1626 warn "RMDIR $dir\n";
1635 $Dir_Task_For{$dir} = $task;
1640 #############################################################################
1642 # General Utilities: nothing stow specific here.
1644 #############################################################################
1646 #===== SUBROUTINE ============================================================
1647 # Name : strip_quotes
1648 # Purpose : remove matching outer quotes from the given string
1651 # Throws : no exceptions
1653 #=============================================================================
1658 if ($string =~ m{\A\s*'(.*)'\s*\z}) {
1661 elsif ($string =~ m{\A\s*"(.*)"\s*\z}) {
1667 #===== SUBROUTINE ===========================================================
1668 # Name : relative_path()
1669 # Purpose : find the relative path between two given paths
1670 # Parameters: path1 => a directory path
1671 # : path2 => a directory path
1672 # Returns : path2 relative to path1
1674 # Comments : only used once by main interactive routine
1675 # : factored out for testing
1676 #============================================================================
1679 my ($path1, $path2) = @_;
1681 my (@path1) = split m{/+}, $path1;
1682 my (@path2) = split m{/+}, $path2;
1684 # drop common prefixes until we find a difference
1686 while ( @path1 && @path2 ) {
1687 last PREFIX if $path1[0] ne $path2[0];
1692 # prepend one '..' to $path2 for each component of $path1
1693 while ( shift @path1 ) {
1694 unshift @path2, '..';
1697 return join_paths(@path2);
1700 #===== SUBROUTINE ===========================================================
1701 # Name : join_path()
1702 # Purpose : concatenates given paths
1703 # Parameters: path1, path2, ... => paths
1704 # Returns : concatenation of given paths
1706 # Comments : factors out redundant path elements:
1707 # : '//' => '/' and 'a/b/../c' => 'a/c'
1708 #============================================================================
1713 # weed out empty components and concatenate
1714 my $result = join '/', grep {!/\A\z/} @paths;
1716 # factor out back references and remove redundant /'s)
1719 for my $part ( split m{/+}, $result) {
1720 next PART if $part eq '.';
1721 if (@result && $part eq '..' && $result[-1] ne '..') {
1725 push @result, $part;
1729 return join '/', @result;
1732 #===== SUBROUTINE ===========================================================
1734 # Purpose : find the parent of the given path
1735 # Parameters: @path => components of the path
1736 # Returns : returns a path string
1738 # Comments : allows you to send multiple chunks of the path
1739 # : (this feature is currently not used)
1740 #============================================================================
1743 my $path = join '/', @_;
1744 my @elts = split m{/+}, $path;
1746 return join '/', @elts;
1749 #===== SUBROUTINE ===========================================================
1750 # Name : internal_error()
1751 # Purpose : output internal error message in a consistent form and die
1752 # Parameters: $message => error message to output
1756 #============================================================================
1757 sub internal_error {
1758 my ($format,@args) = @_;
1759 die "$ProgramName: INTERNAL ERROR: ".sprintf($format,@args)."\n",
1760 "This _is_ a bug. Please submit a bug report so we can fix it:-)\n";
1763 #===== SUBROUTINE ===========================================================
1765 # Purpose : output error message in a consistent form and die
1766 # Parameters: $message => error message to output
1770 #============================================================================
1772 my ($format,@args) = @_;
1773 die "$ProgramName: ERROR: ".sprintf($format,@args)." ($!)\n";
1776 #===== SUBROUTINE ===========================================================
1778 # Purpose : print this programs verison and exit
1783 #============================================================================
1785 print "$ProgramName (GNU Stow) version $Version\n";
1789 1; # return true so we can load this script as a module during unit testing