dotfiles: switch {un,}stow_{contents,node}() recursion parameters
Stow walks the package and target tree hierarchies by using mutually recursive pairs of functions: - `stow_contents()` and `stow_node()` - `unstow_contents()` and `unstow_node()` As Stow runs its planning from the target directory (`plan_*()` both call `within_target_do()`), previously the parameters for these included: - `$target_subpath` (or `$target_subdir` in the `*_node()` functions): the relative path from the target top-level directory to the target subdirectory (initially `.` at the beginning of recursion). For example, this could be `dir1/subdir1/file1`. - `$source`: the relative path from the target _subdirectory_ (N.B. _not_ top-level directory) to the package subdirectory. For example, if the relative path to the Stow directory is `../stow`, this could be `../../../stow/pkg1/dir1/subdir1/file1`. This is used when stowing to construct a new link, or when unstowing to detect whether the link can be unstowed. Each time it descends into a further subdirectory of the target and package, it appends the new path segment onto both of these, and also prefixes `$source` with another `..`. When the `--dotfiles` parameter is enabled, it adjusts `$target_subdir`, performing the `dot-foo` => `.foo` adjustment on all segments of the path in one go. In this case, `$target_subpath` could be something like `.dir1/subdir1/file1`, and the corresponding `$source` could be something like `../../../stow/pkg1/dot-dir1/subdir1/file1`. However this doesn't leave an easy way to obtain the relative path from the target _top-level_ directory to the package subdirectory (i.e. `../stow/pkg1/dot-dir1/subdir1/file1`), which is needed for checking its existence and if necessary iterating over its contents. The current implementation solves this by including an extra `$level` parameter which tracks the recursion depth, and uses that to strip the right number of leading path segments off the front of `$source`. (In the above example, it would remove `../..`.) This implementation isn't the most elegant because: - It involves adding things to `$source` and then removing them again. - It performs the `dot-` => `.` adjustment on every path segment at each level, which is overkill, since when recursing down a level, only adjustment on the final subdirectory is required since the higher segments have already had any required adjustment. This in turn requires `adjust_dotfile` to be more complex than it needs to be. It also prevents a potential future where we might want Stow to optionally start iterating from within a subdirectory of the whole package install image / target tree, avoiding adjustment at higher levels and only doing it at the levels below the starting point. - It requires passing an extra `$level` parameter which can be automatically calculated simply by counting the number of slashes in `$target_subpath`. So change the `$source` recursion parameter to instead track the relative path from the top-level package directory to the package subdirectory or file being considered for (un)stowing, and rename it to avoid the ambiguity caused by the word "source". Also automatically calculate the depth simply by counting the number of slashes, and reconstruct `$source` when needed by combining the relative path to the Stow directory with the package name and `$target_subpath`. Closes #33.
This commit is contained in:
parent
744ba651f5
commit
afa50077c9
3 changed files with 162 additions and 120 deletions
255
lib/Stow.pm.in
255
lib/Stow.pm.in
|
@ -284,7 +284,7 @@ sub plan_unstow {
|
||||||
$self->unstow_contents(
|
$self->unstow_contents(
|
||||||
$package,
|
$package,
|
||||||
'.',
|
'.',
|
||||||
$pkg_path,
|
'.',
|
||||||
);
|
);
|
||||||
debug(2, 0, "Planning unstow of package $package... done");
|
debug(2, 0, "Planning unstow of package $package... done");
|
||||||
$self->{action_count}++;
|
$self->{action_count}++;
|
||||||
|
@ -319,8 +319,7 @@ sub plan_stow {
|
||||||
$self->{stow_path},
|
$self->{stow_path},
|
||||||
$package,
|
$package,
|
||||||
'.',
|
'.',
|
||||||
$pkg_path, # source from target
|
'.',
|
||||||
0,
|
|
||||||
);
|
);
|
||||||
debug(2, 0, "Planning stow of package $package... done");
|
debug(2, 0, "Planning stow of package $package... done");
|
||||||
$self->{action_count}++;
|
$self->{action_count}++;
|
||||||
|
@ -361,7 +360,7 @@ sub within_target_do {
|
||||||
debug(3, 0, "cwd restored to $cwd");
|
debug(3, 0, "cwd restored to $cwd");
|
||||||
}
|
}
|
||||||
|
|
||||||
=head2 stow_contents($stow_path, $package, $target_subdir, $source)
|
=head2 stow_contents($stow_path, $package, $pkg_subdir, $target_subdir)
|
||||||
|
|
||||||
Stow the contents of the given directory.
|
Stow the contents of the given directory.
|
||||||
|
|
||||||
|
@ -379,55 +378,48 @@ Stow Directories" section of the manual).
|
||||||
|
|
||||||
The package whose contents are being stowed.
|
The package whose contents are being stowed.
|
||||||
|
|
||||||
|
=item $pkg_subdir
|
||||||
|
|
||||||
|
Subdirectory of the installation image in the package directory which
|
||||||
|
needs stowing as a symlink which points to it. This is relative to
|
||||||
|
the top-level package directory.
|
||||||
|
|
||||||
=item $target_subdir
|
=item $target_subdir
|
||||||
|
|
||||||
Subpath relative to package directory which needs stowing as a symlink
|
Subdirectory of the target directory which either needs a symlink to the
|
||||||
at subpath relative to target directory.
|
corresponding package subdirectory in the installation image, or if
|
||||||
|
it's an existing directory, it's an unfolded tree which may need to
|
||||||
=item $source
|
be folded or recursed into.
|
||||||
|
|
||||||
Relative path from the (sub)dir of target to symlink source.
|
|
||||||
|
|
||||||
=back
|
=back
|
||||||
|
|
||||||
C<stow_node()> and C<stow_contents()> are mutually recursive.
|
C<stow_node()> and C<stow_contents()> are mutually recursive.
|
||||||
C<$source> and C<$target_subdir> are used for creating the symlink.
|
|
||||||
|
|
||||||
=cut
|
=cut
|
||||||
|
|
||||||
sub stow_contents {
|
sub stow_contents {
|
||||||
my $self = shift;
|
my $self = shift;
|
||||||
my ($stow_path, $package, $target_subdir, $source, $level) = @_;
|
my ($stow_path, $package, $pkg_subdir, $target_subdir) = @_;
|
||||||
|
|
||||||
|
return if $self->should_skip_target($pkg_subdir);
|
||||||
|
|
||||||
|
my $cwd = getcwd();
|
||||||
|
my $msg = "Stowing contents of $stow_path / $package / $pkg_subdir (cwd=$cwd)";
|
||||||
|
$msg =~ s!$ENV{HOME}(/|$)!~$1!g;
|
||||||
|
debug(3, 0, $msg);
|
||||||
|
debug(4, 1, "target subdir is $target_subdir");
|
||||||
|
|
||||||
# Calculate the path to the package directory or sub-directory
|
# Calculate the path to the package directory or sub-directory
|
||||||
# whose contents need to be stowed, relative to the current
|
# whose contents need to be stowed, relative to the current
|
||||||
# (target directory). This is needed so that we can check it's a
|
# (target directory). This is needed so that we can check it's a
|
||||||
# valid directory, and can read its contents to iterate over them.
|
# valid directory, and can read its contents to iterate over them.
|
||||||
#
|
my $pkg_path_from_cwd = join_paths($stow_path, $package, $pkg_subdir);
|
||||||
# Note that $source refers to the same package (sub-)directory,
|
|
||||||
# but instead it's relative to the target directory or
|
|
||||||
# sub-directory where the symlink will be installed when the plans
|
|
||||||
# are executed.
|
|
||||||
|
|
||||||
# Remove leading $level times .. from $source
|
|
||||||
my $n = 0;
|
|
||||||
my $path = join '/', map { (++$n <= $level) ? ( ) : $_ } (split m{/+}, $source);
|
|
||||||
|
|
||||||
return if $self->should_skip_target($target_subdir);
|
|
||||||
|
|
||||||
my $cwd = getcwd();
|
|
||||||
my $msg = "Stowing contents of $path (cwd=$cwd)";
|
|
||||||
$msg =~ s!$ENV{HOME}(/|$)!~$1!g;
|
|
||||||
debug(3, 0, $msg);
|
|
||||||
debug(4, 1, "=> $source");
|
|
||||||
|
|
||||||
error("stow_contents() called with non-directory package path: $path")
|
|
||||||
unless -d $path;
|
|
||||||
error("stow_contents() called with non-directory target: $target_subdir")
|
error("stow_contents() called with non-directory target: $target_subdir")
|
||||||
unless $self->is_a_node($target_subdir);
|
unless $self->is_a_node($target_subdir);
|
||||||
|
|
||||||
opendir my $DIR, $path
|
opendir my $DIR, $pkg_path_from_cwd
|
||||||
or error("cannot read directory: $path ($!)");
|
or error("cannot read directory: $pkg_path_from_cwd ($!)");
|
||||||
my @listing = readdir $DIR;
|
my @listing = readdir $DIR;
|
||||||
closedir $DIR;
|
closedir $DIR;
|
||||||
|
|
||||||
|
@ -435,26 +427,31 @@ sub stow_contents {
|
||||||
for my $node (sort @listing) {
|
for my $node (sort @listing) {
|
||||||
next NODE if $node eq '.';
|
next NODE if $node eq '.';
|
||||||
next NODE if $node eq '..';
|
next NODE if $node eq '..';
|
||||||
my $node_target = join_paths($target_subdir, $node);
|
|
||||||
next NODE if $self->ignore($stow_path, $package, $node_target);
|
my $package_node_path = join_paths($pkg_subdir, $node);
|
||||||
|
my $target_node = $node;
|
||||||
|
|
||||||
if ($self->{dotfiles}) {
|
if ($self->{dotfiles}) {
|
||||||
my $adj_node_target = adjust_dotfile($node_target);
|
my $adjusted = adjust_dotfile($node);
|
||||||
debug(4, 1, "Adjusting: $node_target => $adj_node_target");
|
if ($adjusted ne $node) {
|
||||||
$node_target = $adj_node_target;
|
debug(4, 1, "Adjusting: $node => $adjusted");
|
||||||
|
$target_node = $adjusted;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
my $target_node_path = join_paths($target_subdir, $target_node);
|
||||||
|
|
||||||
|
next NODE if $self->ignore($stow_path, $package, $target_node_path);
|
||||||
|
|
||||||
$self->stow_node(
|
$self->stow_node(
|
||||||
$stow_path,
|
$stow_path,
|
||||||
$package,
|
$package,
|
||||||
$node_target, # target, potentially adjusted for dot- prefix
|
$package_node_path,
|
||||||
join_paths($source, $node), # source
|
$target_node_path
|
||||||
$level
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
=head2 stow_node($stow_path, $package, $target_subpath, $source)
|
=head2 stow_node($stow_path, $package, $pkg_subpath, $target_subpath)
|
||||||
|
|
||||||
Stow the given node
|
Stow the given node
|
||||||
|
|
||||||
|
@ -470,16 +467,20 @@ Stow Directories" section of the manual).
|
||||||
|
|
||||||
=item $package
|
=item $package
|
||||||
|
|
||||||
The package containing the node being stowed
|
The package containing the node being stowed.
|
||||||
|
|
||||||
|
=item $pkg_subpath
|
||||||
|
|
||||||
|
Subpath of the installation image in the package directory which needs
|
||||||
|
stowing as a symlink which points to it. This is relative to the
|
||||||
|
top-level package directory.
|
||||||
|
|
||||||
=item $target_subpath
|
=item $target_subpath
|
||||||
|
|
||||||
Subpath relative to package directory of node which needs stowing as a
|
Subpath of the target directory which either needs a symlink to the
|
||||||
symlink at subpath relative to target directory.
|
corresponding package subpathectory in the installation image, or if
|
||||||
|
it's an existing directory, it's an unfolded tree which may need to
|
||||||
=item $source
|
be folded or recursed into.
|
||||||
|
|
||||||
Relative path to symlink source from the dir of target.
|
|
||||||
|
|
||||||
=back
|
=back
|
||||||
|
|
||||||
|
@ -489,27 +490,42 @@ C<stow_node()> and C<stow_contents()> are mutually recursive.
|
||||||
|
|
||||||
sub stow_node {
|
sub stow_node {
|
||||||
my $self = shift;
|
my $self = shift;
|
||||||
my ($stow_path, $package, $target_subpath, $source, $level) = @_;
|
my ($stow_path, $package, $pkg_subpath, $target_subpath) = @_;
|
||||||
|
|
||||||
my $path = join_paths($stow_path, $package, $target_subpath);
|
debug(3, 0, "Stowing entry $stow_path / $package / $pkg_subpath");
|
||||||
|
# Calculate the path to the package directory or sub-directory
|
||||||
debug(3, 0, "Stowing entry $stow_path / $package / $target_subpath");
|
# whose contents need to be stowed, relative to the current
|
||||||
debug(4, 1, "=> $source");
|
# (target directory). This is needed so that we can check it's a
|
||||||
|
# valid directory, and can read its contents to iterate over them.
|
||||||
|
my $pkg_path_from_cwd = join_paths($stow_path, $package, $pkg_subpath);
|
||||||
|
|
||||||
# Don't try to stow absolute symlinks (they can't be unstowed)
|
# Don't try to stow absolute symlinks (they can't be unstowed)
|
||||||
if (-l $source) {
|
if (-l $pkg_path_from_cwd) {
|
||||||
my $link_dest = $self->read_a_link($source);
|
my $link_dest = $self->read_a_link($pkg_path_from_cwd);
|
||||||
if ($link_dest =~ m{\A/}) {
|
if ($link_dest =~ m{\A/}) {
|
||||||
$self->conflict(
|
$self->conflict(
|
||||||
'stow',
|
'stow',
|
||||||
$package,
|
$package,
|
||||||
"source is an absolute symlink $source => $link_dest"
|
"source is an absolute symlink $pkg_path_from_cwd => $link_dest"
|
||||||
);
|
);
|
||||||
debug(3, 0, "Absolute symlinks cannot be unstowed");
|
debug(3, 0, "Absolute symlinks cannot be unstowed");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# How many directories deep are we?
|
||||||
|
my $level = ($pkg_subpath =~ tr,/,,);
|
||||||
|
debug(2, 1, "level of $pkg_subpath is $level");
|
||||||
|
|
||||||
|
# Calculate the destination of the symlink which would need to be
|
||||||
|
# installed within this directory in the absence of folding. This
|
||||||
|
# is relative to the target (sub-)directory where the symlink will
|
||||||
|
# be installed when the plans are executed, so as we descend down
|
||||||
|
# into the package hierarchy, it will have extra "../" segments
|
||||||
|
# prefixed to it.
|
||||||
|
my $link_dest = join_paths('../' x $level, $pkg_path_from_cwd);
|
||||||
|
debug(4, 1, "link destination $link_dest");
|
||||||
|
|
||||||
# Does the target already exist?
|
# Does the target already exist?
|
||||||
if ($self->is_a_link($target_subpath)) {
|
if ($self->is_a_link($target_subpath)) {
|
||||||
# Where is the link pointing?
|
# Where is the link pointing?
|
||||||
|
@ -533,8 +549,8 @@ sub stow_node {
|
||||||
|
|
||||||
# Does the existing $target_subpath actually point to anything?
|
# Does the existing $target_subpath actually point to anything?
|
||||||
if ($self->is_a_node($existing_pkg_path_from_cwd)) {
|
if ($self->is_a_node($existing_pkg_path_from_cwd)) {
|
||||||
if ($existing_link_dest eq $source) {
|
if ($existing_link_dest eq $link_dest) {
|
||||||
debug(2, 0, "--- Skipping $target_subpath as it already points to $source");
|
debug(2, 0, "--- Skipping $target_subpath as it already points to $link_dest");
|
||||||
}
|
}
|
||||||
elsif ($self->defer($target_subpath)) {
|
elsif ($self->defer($target_subpath)) {
|
||||||
debug(2, 0, "--- Deferring installation of: $target_subpath");
|
debug(2, 0, "--- Deferring installation of: $target_subpath");
|
||||||
|
@ -542,10 +558,10 @@ sub stow_node {
|
||||||
elsif ($self->override($target_subpath)) {
|
elsif ($self->override($target_subpath)) {
|
||||||
debug(2, 0, "--- Overriding installation of: $target_subpath");
|
debug(2, 0, "--- Overriding installation of: $target_subpath");
|
||||||
$self->do_unlink($target_subpath);
|
$self->do_unlink($target_subpath);
|
||||||
$self->do_link($source, $target_subpath);
|
$self->do_link($link_dest, $target_subpath);
|
||||||
}
|
}
|
||||||
elsif ($self->is_a_dir(join_paths(parent($target_subpath), $existing_link_dest)) &&
|
elsif ($self->is_a_dir(join_paths(parent($target_subpath), $existing_link_dest)) &&
|
||||||
$self->is_a_dir(join_paths(parent($target_subpath), $source)))
|
$self->is_a_dir(join_paths(parent($target_subpath), $link_dest)))
|
||||||
{
|
{
|
||||||
|
|
||||||
# If the existing link points to a directory,
|
# If the existing link points to a directory,
|
||||||
|
@ -558,16 +574,14 @@ sub stow_node {
|
||||||
$self->stow_contents(
|
$self->stow_contents(
|
||||||
$existing_stow_path,
|
$existing_stow_path,
|
||||||
$existing_package,
|
$existing_package,
|
||||||
|
$pkg_subpath,
|
||||||
$target_subpath,
|
$target_subpath,
|
||||||
join_paths('..', $existing_link_dest),
|
|
||||||
$level + 1,
|
|
||||||
);
|
);
|
||||||
$self->stow_contents(
|
$self->stow_contents(
|
||||||
$self->{stow_path},
|
$self->{stow_path},
|
||||||
$package,
|
$package,
|
||||||
|
$pkg_subpath,
|
||||||
$target_subpath,
|
$target_subpath,
|
||||||
join_paths('..', $source),
|
|
||||||
$level + 1,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
@ -581,9 +595,9 @@ sub stow_node {
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
# The existing link is invalid, so replace it with a good link
|
# The existing link is invalid, so replace it with a good link
|
||||||
debug(2, 0, "--- replacing invalid link: $path");
|
debug(2, 0, "--- replacing invalid link: $target_subpath");
|
||||||
$self->do_unlink($target_subpath);
|
$self->do_unlink($target_subpath);
|
||||||
$self->do_link($source, $target_subpath);
|
$self->do_link($link_dest, $target_subpath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
elsif ($self->is_a_node($target_subpath)) {
|
elsif ($self->is_a_node($target_subpath)) {
|
||||||
|
@ -592,15 +606,14 @@ sub stow_node {
|
||||||
$self->stow_contents(
|
$self->stow_contents(
|
||||||
$self->{stow_path},
|
$self->{stow_path},
|
||||||
$package,
|
$package,
|
||||||
|
$pkg_subpath,
|
||||||
$target_subpath,
|
$target_subpath,
|
||||||
join_paths('..', $source),
|
|
||||||
$level + 1,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if ($self->{adopt}) {
|
if ($self->{adopt}) {
|
||||||
$self->do_mv($target_subpath, $path);
|
$self->do_mv($target_subpath, $pkg_path_from_cwd);
|
||||||
$self->do_link($source, $target_subpath);
|
$self->do_link($link_dest, $target_subpath);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
$self->conflict(
|
$self->conflict(
|
||||||
|
@ -611,18 +624,17 @@ sub stow_node {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
elsif ($self->{'no-folding'} && -d $path && ! -l $path) {
|
elsif ($self->{'no-folding'} && -d $pkg_path_from_cwd && ! -l $pkg_path_from_cwd) {
|
||||||
$self->do_mkdir($target_subpath);
|
$self->do_mkdir($target_subpath);
|
||||||
$self->stow_contents(
|
$self->stow_contents(
|
||||||
$self->{stow_path},
|
$self->{stow_path},
|
||||||
$package,
|
$package,
|
||||||
|
$pkg_subpath,
|
||||||
$target_subpath,
|
$target_subpath,
|
||||||
join_paths('..', $source),
|
|
||||||
$level + 1,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
$self->do_link($source, $target_subpath);
|
$self->do_link($link_dest, $target_subpath);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -684,7 +696,7 @@ sub marked_stow_dir {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
=head2 unstow_contents($package, $target)
|
=head2 unstow_contents($package, $pkg_subdir, $target_subdir)
|
||||||
|
|
||||||
Unstow the contents of the given directory
|
Unstow the contents of the given directory
|
||||||
|
|
||||||
|
@ -694,9 +706,18 @@ Unstow the contents of the given directory
|
||||||
|
|
||||||
The package whose contents are being unstowed.
|
The package whose contents are being unstowed.
|
||||||
|
|
||||||
=item $target
|
=item $pkg_subdir
|
||||||
|
|
||||||
Relative path to symlink target from the current directory.
|
Subdirectory of the installation image in the package directory which
|
||||||
|
may need a symlink pointing to it to be unstowed. This is relative to
|
||||||
|
the top-level package directory.
|
||||||
|
|
||||||
|
=item $target_subdir
|
||||||
|
|
||||||
|
Subdirectory of the target directory which either needs unstowing of a
|
||||||
|
symlink to the corresponding package subdirectory in the installation
|
||||||
|
image, or if it's an existing directory, it's an unfolded tree which
|
||||||
|
may need to be recursed into.
|
||||||
|
|
||||||
=back
|
=back
|
||||||
|
|
||||||
|
@ -707,15 +728,21 @@ Here we traverse the package tree, rather than the target tree.
|
||||||
|
|
||||||
sub unstow_contents {
|
sub unstow_contents {
|
||||||
my $self = shift;
|
my $self = shift;
|
||||||
my ($package, $target_subdir, $path) = @_;
|
my ($package, $pkg_subdir, $target_subdir) = @_;
|
||||||
|
|
||||||
return if $self->should_skip_target($target_subdir);
|
return if $self->should_skip_target($target_subdir);
|
||||||
|
|
||||||
my $cwd = getcwd();
|
my $cwd = getcwd();
|
||||||
my $msg = "Unstowing from $target_subdir (cwd=$cwd, stow dir=$self->{stow_path})";
|
my $msg = "Unstowing contents of $self->{stow_path} / $package / $pkg_subdir (cwd=$cwd" . ($self->{compat} ? ', compat' : '') . ")";
|
||||||
$msg =~ s!$ENV{HOME}/!~/!g;
|
$msg =~ s!$ENV{HOME}/!~/!g;
|
||||||
debug(3, 0, $msg);
|
debug(3, 0, $msg);
|
||||||
debug(4, 1, "source path is $path");
|
debug(4, 1, "target subdir is $target_subdir");
|
||||||
|
|
||||||
|
# Calculate the path to the package directory or sub-directory
|
||||||
|
# whose contents need to be unstowed, relative to the current
|
||||||
|
# (target directory). This is needed so that we can check it's a
|
||||||
|
# valid directory, and can read its contents to iterate over them.
|
||||||
|
my $pkg_path_from_cwd = join_paths($self->{stow_path}, $package, $pkg_subdir);
|
||||||
|
|
||||||
if ($self->{compat}) {
|
if ($self->{compat}) {
|
||||||
# In compat mode we traverse the target tree not the source tree,
|
# In compat mode we traverse the target tree not the source tree,
|
||||||
|
@ -725,9 +752,10 @@ sub unstow_contents {
|
||||||
unless -d $target_subdir;
|
unless -d $target_subdir;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
# We traverse the source tree not the target tree, so $path must exist.
|
# We traverse the package installation image tree not the
|
||||||
error("unstow_contents() called with non-directory path: $path")
|
# target tree, so $pkg_path_from_cwd must exist.
|
||||||
unless -d $path;
|
error("unstow_contents() called with non-directory path: $pkg_path_from_cwd")
|
||||||
|
unless -d $pkg_path_from_cwd;
|
||||||
|
|
||||||
# When called at the top level, $target_subdir should exist. And
|
# When called at the top level, $target_subdir should exist. And
|
||||||
# unstow_node() should only call this via mutual recursion if
|
# unstow_node() should only call this via mutual recursion if
|
||||||
|
@ -736,7 +764,7 @@ sub unstow_contents {
|
||||||
unless $self->is_a_node($target_subdir);
|
unless $self->is_a_node($target_subdir);
|
||||||
}
|
}
|
||||||
|
|
||||||
my $dir = $self->{compat} ? $target_subdir : $path;
|
my $dir = $self->{compat} ? $target_subdir : $pkg_path_from_cwd;
|
||||||
opendir my $DIR, $dir
|
opendir my $DIR, $dir
|
||||||
or error("cannot read directory: $dir ($!)");
|
or error("cannot read directory: $dir ($!)");
|
||||||
my @listing = readdir $DIR;
|
my @listing = readdir $DIR;
|
||||||
|
@ -746,16 +774,29 @@ sub unstow_contents {
|
||||||
for my $node (sort @listing) {
|
for my $node (sort @listing) {
|
||||||
next NODE if $node eq '.';
|
next NODE if $node eq '.';
|
||||||
next NODE if $node eq '..';
|
next NODE if $node eq '..';
|
||||||
my $node_target = join_paths($target_subdir, $node);
|
|
||||||
next NODE if $self->ignore($self->{stow_path}, $package, $node_target);
|
my $package_node = $node;
|
||||||
|
my $target_node = $node;
|
||||||
|
|
||||||
if ($self->{dotfiles}) {
|
if ($self->{dotfiles}) {
|
||||||
my $adj_node_target = adjust_dotfile($node_target);
|
# $node is in the package tree, so adjust any dot-*
|
||||||
debug(4, 1, "Adjusting: $node_target => $adj_node_target");
|
# files for the target.
|
||||||
$node_target = $adj_node_target;
|
my $adjusted = adjust_dotfile($node);
|
||||||
|
if ($adjusted ne $node) {
|
||||||
|
debug(4, 1, "Adjusting: $node => $adjusted");
|
||||||
|
$target_node = $adjusted;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
my $package_node_path = join_paths($pkg_subdir, $package_node);
|
||||||
|
my $target_node_path = join_paths($target_subdir, $target_node);
|
||||||
|
|
||||||
$self->unstow_node($package, $node_target, join_paths($path, $node));
|
next NODE if $self->ignore($self->{stow_path}, $package, $target_node_path);
|
||||||
|
|
||||||
|
$self->unstow_node(
|
||||||
|
$package,
|
||||||
|
$package_node_path,
|
||||||
|
$target_node_path
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (! $self->{compat} && -d $target_subdir) {
|
if (! $self->{compat} && -d $target_subdir) {
|
||||||
|
@ -763,7 +804,7 @@ sub unstow_contents {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
=head2 unstow_node($package, $target_subpath)
|
=head2 unstow_node($package, $pkg_subpath, $target_subpath)
|
||||||
|
|
||||||
Unstow the given node.
|
Unstow the given node.
|
||||||
|
|
||||||
|
@ -773,9 +814,18 @@ Unstow the given node.
|
||||||
|
|
||||||
The package containing the node being unstowed.
|
The package containing the node being unstowed.
|
||||||
|
|
||||||
|
=item $pkg_subpath
|
||||||
|
|
||||||
|
Subpath of the installation image in the package directory which needs
|
||||||
|
stowing as a symlink which points to it. This is relative to the
|
||||||
|
top-level package directory.
|
||||||
|
|
||||||
=item $target_subpath
|
=item $target_subpath
|
||||||
|
|
||||||
Relative path to symlink target from the current directory.
|
Subpath of the target directory which either needs a symlink to the
|
||||||
|
corresponding package subpathectory in the installation image, or if
|
||||||
|
it's an existing directory, it's an unfolded tree which may need to
|
||||||
|
be folded or recursed into.
|
||||||
|
|
||||||
=back
|
=back
|
||||||
|
|
||||||
|
@ -785,17 +835,19 @@ C<unstow_node()> and C<unstow_contents()> are mutually recursive.
|
||||||
|
|
||||||
sub unstow_node {
|
sub unstow_node {
|
||||||
my $self = shift;
|
my $self = shift;
|
||||||
my ($package, $target_subpath, $source) = @_;
|
my ($package, $pkg_subpath, $target_subpath) = @_;
|
||||||
|
|
||||||
debug(3, 1, "Unstowing $source");
|
|
||||||
debug(4, 2, "target is $target_subpath");
|
|
||||||
|
|
||||||
|
debug(3, 0, "Unstowing entry from target: $target_subpath");
|
||||||
|
debug(4, 1, "Package entry: $self->{stow_path} / $package / $pkg_subpath");
|
||||||
|
# Calculate the path to the package directory or sub-directory
|
||||||
|
# whose contents need to be unstowed, relative to the current
|
||||||
|
# (target directory).
|
||||||
# Does the target exist?
|
# Does the target exist?
|
||||||
if ($self->is_a_link($target_subpath)) {
|
if ($self->is_a_link($target_subpath)) {
|
||||||
$self->unstow_link_node($package, $target_subpath, $source);
|
$self->unstow_link_node($package, $pkg_subpath, $target_subpath);
|
||||||
}
|
}
|
||||||
elsif (-d $target_subpath) {
|
elsif (-d $target_subpath) {
|
||||||
$self->unstow_contents($package, $target_subpath, $source);
|
$self->unstow_contents($package, $pkg_subpath, $target_subpath);
|
||||||
|
|
||||||
# This action may have made the parent directory foldable
|
# This action may have made the parent directory foldable
|
||||||
if (my $parent_in_pkg = $self->foldable($target_subpath)) {
|
if (my $parent_in_pkg = $self->foldable($target_subpath)) {
|
||||||
|
@ -812,7 +864,7 @@ sub unstow_node {
|
||||||
|
|
||||||
sub unstow_link_node {
|
sub unstow_link_node {
|
||||||
my $self = shift;
|
my $self = shift;
|
||||||
my ($package, $target_subpath, $pkg_path_from_cwd) = @_;
|
my ($package, $pkg_subpath, $target_subpath) = @_;
|
||||||
debug(4, 2, "Evaluate existing link: $target_subpath");
|
debug(4, 2, "Evaluate existing link: $target_subpath");
|
||||||
|
|
||||||
# Where is the link pointing?
|
# Where is the link pointing?
|
||||||
|
@ -837,9 +889,12 @@ sub unstow_link_node {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
my $pkg_path_from_cwd = join_paths($self->{stow_path}, $package, $pkg_subpath);
|
||||||
|
|
||||||
# Does the existing $target_subpath actually point to anything?
|
# Does the existing $target_subpath actually point to anything?
|
||||||
if (-e $existing_pkg_path_from_cwd) {
|
if (-e $existing_pkg_path_from_cwd) {
|
||||||
if ($existing_pkg_path_from_cwd eq $pkg_path_from_cwd) {
|
if ($existing_pkg_path_from_cwd eq $pkg_path_from_cwd) {
|
||||||
|
# It points to the package we're unstowing, so unstow the link.
|
||||||
$self->do_unlink($target_subpath);
|
$self->do_unlink($target_subpath);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|
|
@ -239,17 +239,9 @@ sub restore_cwd {
|
||||||
}
|
}
|
||||||
|
|
||||||
sub adjust_dotfile {
|
sub adjust_dotfile {
|
||||||
my ($link_dest) = @_;
|
my ($pkg_node) = @_;
|
||||||
|
(my $adjusted = $pkg_node) =~ s/^dot-([^.])/.$1/;
|
||||||
my @result = ();
|
return $adjusted;
|
||||||
for my $part (split m{/+}, $link_dest) {
|
|
||||||
if (($part ne "dot-") && ($part ne "dot-.")) {
|
|
||||||
$part =~ s/^dot-/./;
|
|
||||||
}
|
|
||||||
push @result, $part;
|
|
||||||
}
|
|
||||||
|
|
||||||
return join '/', @result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
=head1 BUGS
|
=head1 BUGS
|
||||||
|
|
13
t/dotfiles.t
13
t/dotfiles.t
|
@ -22,7 +22,7 @@
|
||||||
use strict;
|
use strict;
|
||||||
use warnings;
|
use warnings;
|
||||||
|
|
||||||
use Test::More tests => 10;
|
use Test::More tests => 11;
|
||||||
use English qw(-no_match_vars);
|
use English qw(-no_match_vars);
|
||||||
|
|
||||||
use Stow::Util qw(adjust_dotfile);
|
use Stow::Util qw(adjust_dotfile);
|
||||||
|
@ -32,17 +32,12 @@ init_test_dirs();
|
||||||
cd("$TEST_DIR/target");
|
cd("$TEST_DIR/target");
|
||||||
|
|
||||||
subtest('adjust_dotfile()', sub {
|
subtest('adjust_dotfile()', sub {
|
||||||
plan tests => 9;
|
plan tests => 4;
|
||||||
my @TESTS = (
|
my @TESTS = (
|
||||||
['file'],
|
['file'],
|
||||||
|
['dot-'],
|
||||||
|
['dot-.'],
|
||||||
['dot-file', '.file'],
|
['dot-file', '.file'],
|
||||||
['dir1/file'],
|
|
||||||
['dir1/dir2/file'],
|
|
||||||
['dir1/dir2/dot-file', 'dir1/dir2/.file'],
|
|
||||||
['dir1/dot-dir2/file', 'dir1/.dir2/file'],
|
|
||||||
['dir1/dot-dir2/dot-file', 'dir1/.dir2/.file'],
|
|
||||||
['dot-dir1/dot-dir2/dot-file', '.dir1/.dir2/.file'],
|
|
||||||
['dot-dir1/dot-dir2/file', '.dir1/.dir2/file'],
|
|
||||||
);
|
);
|
||||||
for my $test (@TESTS) {
|
for my $test (@TESTS) {
|
||||||
my ($input, $expected) = @$test;
|
my ($input, $expected) = @$test;
|
||||||
|
|
Loading…
Reference in a new issue