From 34421ba5cf34df7265017e6564fa4d7922e37182 Mon Sep 17 00:00:00 2001 From: Adam Spiers Date: Tue, 2 Apr 2024 00:06:38 +0100 Subject: [PATCH] stow_contents: fix bugs and corner cases with type mismatch conflicts If the target directory as a file named X and a package has a directory named X, or vice-versa, then it is impossible for Stow to stow that entry X from the package, even if --adopt is supplied. However we were previously only handling the former case, and not the latter, and the test for the former was actually broken. So fix stow_contents() to handle both cases correctly, fix the broken test, and add a new test for the latter case. --- lib/Stow.pm.in | 40 +++++++++++++++++++++++++++++++--------- t/stow.t | 42 +++++++++++++++++++++++++++++++++++------- 2 files changed, 66 insertions(+), 16 deletions(-) diff --git a/lib/Stow.pm.in b/lib/Stow.pm.in index 2c94622..5a81855 100755 --- a/lib/Stow.pm.in +++ b/lib/Stow.pm.in @@ -603,23 +603,45 @@ sub stow_node { elsif ($self->is_a_node($target_subpath)) { debug(4, 1, "Evaluate existing node: $target_subpath"); if ($self->is_a_dir($target_subpath)) { - $self->stow_contents( - $self->{stow_path}, - $package, - $pkg_subpath, - $target_subpath, - ); + if (! -d $pkg_path_from_cwd) { + # FIXME: why wasn't this ever needed before? + $self->conflict( + 'stow', + $package, + "cannot stow non-directory $pkg_path_from_cwd over existing directory target $target_subpath" + ); + } + else { + $self->stow_contents( + $self->{stow_path}, + $package, + $pkg_subpath, + $target_subpath, + ); + } } else { + # If we're here, $target_subpath is not a current or + # planned directory. + if ($self->{adopt}) { - $self->do_mv($target_subpath, $pkg_path_from_cwd); - $self->do_link($link_dest, $target_subpath); + if (-d $pkg_path_from_cwd) { + $self->conflict( + 'stow', + $package, + "cannot stow directory $pkg_path_from_cwd over existing non-directory target $target_subpath" + ); + } + else { + $self->do_mv($target_subpath, $pkg_path_from_cwd); + $self->do_link($link_dest, $target_subpath); + } } else { $self->conflict( 'stow', $package, - "existing target is neither a link nor a directory: $target_subpath" + "cannot stow $pkg_path_from_cwd over existing target $target_subpath since neither a link nor a directory and --adopt not specified" ); } } diff --git a/t/stow.t b/t/stow.t index 318eb6d..d23e8d6 100755 --- a/t/stow.t +++ b/t/stow.t @@ -22,7 +22,7 @@ use strict; use warnings; -use Test::More tests => 21; +use Test::More tests => 22; use Test::Output; use English qw(-no_match_vars); @@ -103,7 +103,7 @@ subtest("Package dir 'bin4' conflicts with existing non-dir so can't unfold", su is($stow->get_conflict_count, 1); like( $conflicts{stow}{pkg4}[0], - qr/existing target is neither a link nor a directory/ + qr!cannot stow ../stow/pkg4/bin4 over existing target bin4 since neither a link nor a directory and --adopt not specified! => 'link to new dir bin4 conflicts with existing non-directory' ); }); @@ -111,8 +111,7 @@ subtest("Package dir 'bin4' conflicts with existing non-dir so can't unfold", su subtest("Package dir 'bin4a' conflicts with existing non-dir " . "so can't unfold even with --adopt", sub { plan tests => 2; - #my $stow = new_Stow(adopt => 1); - my $stow = new_Stow(); + my $stow = new_Stow(adopt => 1); make_file('bin4a'); # this is a file but named like a directory make_path('../stow/pkg4a/bin4a'); @@ -121,8 +120,9 @@ subtest("Package dir 'bin4a' conflicts with existing non-dir " . $stow->plan_stow('pkg4a'); %conflicts = $stow->get_conflicts(); is($stow->get_conflict_count, 1); - like($conflicts{stow}{pkg4a}[0], - qr/existing target is neither a link nor a directory/ + like( + $conflicts{stow}{pkg4a}[0], + qr!cannot stow directory ../stow/pkg4a/bin4a over existing non-directory target bin4a! => 'link to new dir bin4a conflicts with existing non-directory' ); }); @@ -146,14 +146,42 @@ subtest("Package files 'file4b' and 'bin4b' conflict with existing files", sub { %conflicts = $stow->get_conflicts(); is($stow->get_conflict_count, 2 => 'conflict per file'); for my $i (0, 1) { + my $target = $i ? 'file4b' : 'bin4b/file4b'; like( $conflicts{stow}{pkg4b}[$i], - qr/existing target is neither a link nor a directory/ + qr,cannot stow ../stow/pkg4b/$target over existing target $target since neither a link nor a directory and --adopt not specified, => 'link to file4b conflicts with existing non-directory' ); } }); +subtest("Package files 'file4d' conflicts with existing directories", sub { + plan tests => 3; + my $stow = new_Stow(); + + # Populate target + make_path('file4d'); # this is a directory but named like a file to create the conflict + make_path('bin4d/file4d'); # same here + + # Populate stow package + make_path('../stow/pkg4d'); + make_file('../stow/pkg4d/file4d', 'file4d - version originally in stow package'); + make_path('../stow/pkg4d/bin4d'); + make_file('../stow/pkg4d/bin4d/file4d', 'bin4d/file4d - version originally in stow package'); + + $stow->plan_stow('pkg4d'); + %conflicts = $stow->get_conflicts(); + is($stow->get_conflict_count, 2 => 'conflict per file'); + for my $i (0, 1) { + my $target = $i ? 'file4d' : 'bin4d/file4d'; + like( + $conflicts{stow}{pkg4d}[$i], + qr!cannot stow non-directory ../stow/pkg4d/$target over existing directory target $target! + => 'link to file4d conflicts with existing non-directory' + ); + } +}); + subtest("Package files 'file4c' and 'bin4c' can adopt existing versions", sub { plan tests => 8; my $stow = new_Stow(adopt => 1);