Release 2.4.0

-----BEGIN PGP SIGNATURE-----
 
 iQIzBAABCAAdFiEElCuQdazKBOkDfHP+0xtVY9rB1PoFAmYS3jkACgkQ0xtVY9rB
 1PqSxBAAvPPMSrrxdfGAL9anzCDZArRrtplbS1493fj+OiNIjDWSGsJwm1mZX1ny
 i04te8Hne/ppZs/CrYAe0PZGl6LowswBKFuHkft//f9maeXG/zl+Uh4E7dYWhXik
 N0+9cgh/ngKJ5QgQelfNxZGrHTZZmbbnNfrtuMPqGIDGIDSzNw2qlqdFzp9VxRth
 /dbLLAHoRXY2uRhSgSnEhnk+zJyxR9od5tU3p094xyUEhbBkNRzCE4TwA9oGvyFQ
 N6onEo4wj5w8Mqtu5p6BY5IktYIgechJxF86Sqy27UU/uNQyzeaG2Qs+diwScpop
 9eOsEUDY5mi7oLs4KME0SGqF/V54Spv+QDGks7bftvSQf3WQvFWX+w1fYymbtctD
 US1caKlsleODnQGmyXEBPyNwGmsLmbaF03yIW1BtLfibDxKeDXK0e2zznpBHRRGO
 2klSjH+qd2hqDT3eYNP8SLpRPUdWiuHP2fb1LV+VzjO7vR4iPLdjvTddFEsm7u2M
 IknSXtxCzJuBD4Dz9fZ/fbVEp+2wYmbtKgqmcnKSNtZ5GaFrmA96mekAbyVWYwdB
 IHuscAWlZSCwJJVPp3VMVGVdXfbYhLJRC+Ft6QAzJeJZ2Q44dxnmPDLQHpngNXWJ
 3YhZ62j5C0wInTh0NzUiDbGkxHwY1v4xJC4OXrR6k8+qqPOe1J4=
 =SC3E
 -----END PGP SIGNATURE-----
gpgsig -----BEGIN PGP SIGNATURE-----
 
 iQIzBAABCAAdFiEEdnljh8gT62W19LaLaFR4GgSIQhwFAmZdM3cACgkQaFR4GgSI
 Qhx/GA/7BbccrVBYNEnu1TJ9riozvN0k1AO6LE96R7QJQH+hpVcM+al4uIc2+GXA
 gcgoVtdADv7nD3DMtQsJlPxQsS+RFjaipZy3kdLSZyU3EixK/Zy2GtK0zjYLMlI/
 rGEEkPx0um3MBm/gtvfZ/pK9i9v2Au61jn49+z4moPnfc4Gk6ZlRXtggDP4ApHPp
 uHeQmr34AZ+dAcc+cs7bCuZvzxxv97WNB7dfFA8RR3+5cQzkbw+Fjjp4GT1J6ifA
 pn4znVZCdb4HhqjFoQzRi68x61MVdhEq4309Mm8xGi7fTqWgxoRPZjKmgpLUF0Wp
 rUNpmd+whNiebMhi0G1yYl1+Pf0x5Thoa7x66M6YWrSMwbr5KJuU3XFNQAr787ls
 pN3nXimX3jw1Mrp8QP4WQby563jHqG4yzBNn+qX22xXxhZOctDJFUDmGZMHHRD5D
 +GICfmgCEp1Ph/YlP4x34jnC4phoIKRs1pUQuIKZM9ECN5Pg6spbT5BmuOn2AhtF
 dLgdSfXAEhnfhxNzf+uvHgelgeeXpTTZEJbX63XoZelK337f1n9CL4Cbumm3JCNS
 YcgJV5L/lw7jRHcaSnyKY37b6iw5+vOdr5yibgJN1LlCGVEbHdpBOPoFZkBA1cTM
 TouP87RHfb47yu+W8WYnBZ+Tr9M6h1S7gfTJ+qjJf244wWaYLO8=
 =Q/B6
 -----END PGP SIGNATURE-----

Pull in upstream Stow 2.4.0
This commit is contained in:
Danielle McLean 2024-06-03 13:07:34 +10:00
commit 08d7a9f7af
36 changed files with 3200 additions and 9977 deletions

1
.coveralls.yml Normal file
View file

@ -0,0 +1 @@
repo_token: xl1m2EiKjG4YlJQ0KjTTBNDRcAFD0lCVt

6
.dir-locals.el Normal file
View file

@ -0,0 +1,6 @@
((cperl-mode . ((dumb-jump-force-searcher . rg)
(cperl-indent-level . 4)
(cperl-close-paren-offset . -4)
(cperl-indent-subs-specially . nil)
(indent-tabs-mode . nil)
(eval . (auto-fill-mode -1)))))

2
.dumbjump Normal file
View file

@ -0,0 +1,2 @@
+bin/*.in
+lib/*.pm.in

80
.github/workflows/test.yml vendored Normal file
View file

@ -0,0 +1,80 @@
# This file is part of GNU Stow.
#
# GNU Stow is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# GNU Stow is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see https://www.gnu.org/licenses/.
name: Test suite
on:
push:
branches: [master]
pull_request:
branches: [master]
types: [opened, synchronize, reopened, ready_for_review]
jobs:
# call-simple-perl-test:
# uses: perl-actions/github-workflows/.github/workflows/simple-perltester-workflow.yml@main
# with:
# since-perl: 5.14
test:
name: Perl ${{ matrix.perl-version }}
runs-on: ubuntu-latest
strategy:
matrix:
perl-version:
- '5.38'
- '5.36'
- '5.34'
- '5.32'
- '5.30'
container:
# This Docker image should avoid the need to run:
#
# cpanm -n Devel::Cover::Report::Coveralls
image: perldocker/perl-tester:${{ matrix.perl-version }}
steps:
- run: apt-get update && apt-get install -y sudo texinfo texlive
- name: Checkout code
uses: actions/checkout@v2
# - uses: awalsh128/cache-apt-pkgs-action@latest
# with:
# debug: true
# packages: texinfo texlive
# version: 1.0
- run: autoreconf --install
- name: ./configure && make
run: |
eval `perl -V:siteprefix`
# Note: this will complain Test::Output isn't yet installed:
./configure --prefix=$siteprefix && make
# but that's OK because we install it here:
make cpanm
#- name: Run tests
# run: make test
- run: make distcheck
- run: perl Build.PL
- run: ./Build build
- run: cover -test -report coveralls
- run: ./Build distcheck

3
.gitignore vendored
View file

@ -9,7 +9,8 @@
/bin/stow /bin/stow
/doc/stow.info /doc/stow.info
/doc/version.texi /doc/version.texi
tmp-testing-trees/ /playground/
tmp-testing-trees*/
_build/ _build/
autom4te.cache/ autom4te.cache/
blib/ blib/

View file

@ -1,3 +1,7 @@
This file documents the high-level history of Stow, and some of its
major contributors. See also the THANKS file for a more complete list
of contributors.
Stow was originally written by Bob Glickstein <bobg+stow@zanshin.com>, Stow was originally written by Bob Glickstein <bobg+stow@zanshin.com>,
Zanshin Software, Inc. Zanshin Software, Inc.

123
CONTRIBUTING.md Normal file
View file

@ -0,0 +1,123 @@
Contributing to GNU Stow
========================
Development of Stow, and GNU in general, is a volunteer effort, and
you can contribute. If you'd like to get involved, it's a good idea to join
the [stow-devel](https://lists.gnu.org/mailman/listinfo/stow-devel)
mailing list.
Bug reporting
-------------
Please follow the procedure described in [the "Reporting Bugs"
section](https://www.gnu.org/software/stow/manual/html_node/Reporting-Bugs.html#Reporting-Bugs)
of [the manual](README.md#documentation).
Development
-----------
For [development sources](https://savannah.gnu.org/git/?group=stow)
and other information, please see the [Stow project
page](http://savannah.gnu.org/projects/stow/) at
[savannah.gnu.org](http://savannah.gnu.org).
There is also a
[stow-devel](https://lists.gnu.org/mailman/listinfo/stow-devel)
mailing list (see [Mailing lists](README.md#mailing-lists)).
Please be aware that all program source files (excluding the test
suite) end in `.in`, and are pre-processed by `Makefile` into
corresponding files with that prefix stripped before execution. So if
you want to test any modifications to the source, make sure that you
change the `.in` files and then run `make` to regenerate the
pre-processed versions before doing any testing. To avoid forgetting
(which can potentially waste a lot of time debugging the wrong code),
you can automatically run `make` in an infinite loop every second via:
make watch
(You could even use fancier approaches like
[`inotifywait(1)`](https://www.man7.org/linux/man-pages/man1/inotifywait.1.html)
or [Guard](https://guardgem.org/). But those are probably overkill in
this case where the simple `while` loop is plenty good enough.)
Testing
~~~~~~~
The test suite can be found in the [`t/`](t/) subdirectory. You can
run the test suite via:
make check
Tests can be run individually as follows. First you have to ensure
that the `t/`, `bin/`, and `lib/` directories are on Perl's search path.
Assuming that you run all tests from the root of the repository tree,
this will do the job:
export PERL5LIB=t:bin:lib
(Not all tests require all of these, but it's safer to include all of
them.)
Secondly, be aware that if you want to test modifications to the
source files, you will need to run `make watch`, or `make` before each
test run as explained above.
Now running an individual test is as simple as:
perl t/chkstow.t
or with a given debugging verbosity corresponding to the `-v` / `--verbose`
command-line option:
TEST_VERBOSE=4 perl t/chkstow.t
The [`prove(1)` test runner](https://perldoc.perl.org/prove) is another
good alternative which provides several handy extra features. Invocation
is very similar, e.g.:
prove t/stow.t
or to run the whole suite:
prove
However currently there is an issue where this interferes with
`TEST_VERBOSE`.
If you want to create test files for experimentation, it is
recommended to put them in a subdirectory called `playground/` since
this will be automatically ignored by git and the build process,
avoiding any undesirable complications.
Test coverage
~~~~~~~~~~~~~
To view test coverage reports, first ensure that
[`Devel::Cover`](https://metacpan.org/dist/Devel-Cover) is installed.
Then type `make coverage`. The last lines of the output should
include something like:
HTML output written to /home/user/path/to/stow/cover_db/coverage.html
which you can open in a web browser to view the report.
Translating Stow
----------------
Stow is not currently multi-lingual, but patches would be very
gratefully accepted. Please e-mail
[stow-devel](https://lists.gnu.org/mailman/listinfo/stow-devel) if you
intend to work on this.
Maintainers
-----------
Stow is currently being maintained by Adam Spiers. Please use [the
mailing lists](README.md#mailing-lists).
Helping the GNU project
-----------------------
For more general information, please read [How to help
GNU](https://www.gnu.org/help/).

View file

@ -3,6 +3,7 @@ aclocal.m4
automake/install-sh automake/install-sh
automake/mdate-sh automake/mdate-sh
automake/missing automake/missing
automake/texinfo.tex
bin/chkstow bin/chkstow
bin/chkstow.in bin/chkstow.in
bin/stow bin/stow
@ -11,6 +12,7 @@ Build.PL
ChangeLog ChangeLog
configure configure
configure.ac configure.ac
CONTRIBUTING.md
COPYING COPYING
default-ignore-list default-ignore-list
doc/ChangeLog.OLD doc/ChangeLog.OLD
@ -19,7 +21,6 @@ doc/manual.pdf
doc/stow.8 doc/stow.8
doc/stow.info doc/stow.info
doc/stow.texi doc/stow.texi
doc/texinfo.tex
doc/version.texi doc/version.texi
INSTALL.md INSTALL.md
lib/Stow.pm lib/Stow.pm
@ -43,12 +44,12 @@ t/find_stowed_path.t
t/foldable.t t/foldable.t
t/ignore.t t/ignore.t
t/join_paths.t t/join_paths.t
t/link_dest_within_stow_dir.t
t/parent.t t/parent.t
t/stow.t t/stow.t
t/rc_options.t t/rc_options.t
t/testutil.pm t/testutil.pm
t/unstow.t t/unstow.t
t/unstow_orig.t
tools/get-version tools/get-version
THANKS THANKS
TODO TODO

View file

@ -83,7 +83,14 @@
^doc/HOWTO-RELEASE$ ^doc/HOWTO-RELEASE$
# Avoid test files # Avoid test files
tmp-testing-trees tmp-testing-trees*
.travis.yml ^.coveralls.yml
^.github/workflows/
^.travis.yml
^docker/ ^docker/
^[a-zA-Z]*-docker.sh ^[a-zA-Z]*-docker.sh
^playground/
# Avoid development config
^.dir-locals.el
^.dumbjump

View file

@ -4,7 +4,7 @@
"unknown" "unknown"
], ],
"dynamic_config" : 1, "dynamic_config" : 1,
"generated_by" : "Module::Build version 0.4224", "generated_by" : "Module::Build version 0.4234",
"license" : [ "license" : [
"gpl_1" "gpl_1"
], ],
@ -37,11 +37,11 @@
"provides" : { "provides" : {
"Stow" : { "Stow" : {
"file" : "lib/Stow.pm", "file" : "lib/Stow.pm",
"version" : "v2.3.2-fixbug56727" "version" : "v2.4.0"
}, },
"Stow::Util" : { "Stow::Util" : {
"file" : "lib/Stow/Util.pm", "file" : "lib/Stow/Util.pm",
"version" : "v2.3.2-fixbug56727" "version" : "v2.4.0"
} }
}, },
"release_status" : "stable", "release_status" : "stable",
@ -55,6 +55,6 @@
"url" : "git://git.savannah.gnu.org/stow.git" "url" : "git://git.savannah.gnu.org/stow.git"
} }
}, },
"version" : "v2.3.2-fixbug56727", "version" : "v2.4.0",
"x_serialization_backend" : "JSON::PP version 4.00" "x_serialization_backend" : "JSON::PP version 4.16"
} }

View file

@ -9,7 +9,7 @@ build_requires:
configure_requires: configure_requires:
Module::Build: '0' Module::Build: '0'
dynamic_config: 1 dynamic_config: 1
generated_by: 'Module::Build version 0.4224, CPAN::Meta::Converter version 2.150010' generated_by: 'Module::Build version 0.4234, CPAN::Meta::Converter version 2.150010'
license: gpl license: gpl
meta-spec: meta-spec:
url: http://module-build.sourceforge.net/META-spec-v1.4.html url: http://module-build.sourceforge.net/META-spec-v1.4.html
@ -18,10 +18,10 @@ name: Stow
provides: provides:
Stow: Stow:
file: lib/Stow.pm file: lib/Stow.pm
version: v2.3.2-fixbug56727 version: v2.4.0
Stow::Util: Stow::Util:
file: lib/Stow/Util.pm file: lib/Stow/Util.pm
version: v2.3.2-fixbug56727 version: v2.4.0
requires: requires:
Carp: '0' Carp: '0'
IO::File: '0' IO::File: '0'
@ -30,5 +30,5 @@ resources:
homepage: https://savannah.gnu.org/projects/stow homepage: https://savannah.gnu.org/projects/stow
license: http://www.gnu.org/licenses/gpl-2.0.html license: http://www.gnu.org/licenses/gpl-2.0.html
repository: git://git.savannah.gnu.org/stow.git repository: git://git.savannah.gnu.org/stow.git
version: v2.3.2-fixbug56727 version: v2.4.0
x_serialization_backend: 'CPAN::Meta::YAML version 0.018' x_serialization_backend: 'CPAN::Meta::YAML version 0.018'

View file

@ -32,26 +32,26 @@ pmstowdir = $(pmdir)/Stow
pm_DATA = lib/Stow.pm pm_DATA = lib/Stow.pm
pmstow_DATA = lib/Stow/Util.pm pmstow_DATA = lib/Stow/Util.pm
TEXINFO_TEX = doc/texinfo.tex
export TEXI2DVI_BUILD_MODE = clean export TEXI2DVI_BUILD_MODE = clean
AM_MAKEINFOFLAGS = -I $(srcdir) AM_MAKEINFOFLAGS = -I $(srcdir)
# We require this -I parameter to ensure that the include of the # We require this -I parameter to ensure that the include of the
# default ignore list in the manual works. Unfortunately this is # default ignore list in the manual works correctly, even when the
# the only way to do it: # manual is being built via make distcheck from a different directory.
# Unfortunately this is the only way to do it:
# #
# http://article.gmane.org/gmane.comp.sysutils.automake.bugs/4334/match=passing+parameters # https://lists.gnu.org/archive/html/bug-automake/2008-09/msg00040.html
# #
# even though it annoyingly produces a warning with the -Wall option # even though it annoyingly produces a warning with the -Wall option
# to AM_INIT_AUTOMAKE which has to be silenced via -Wno-override. # to AM_INIT_AUTOMAKE which has to be silenced via -Wno-override.
TEXI2DVI = texi2dvi $(AM_MAKEINFOFLAGS) TEXI2DVI = texi2dvi $(AM_MAKEINFOFLAGS)
doc_deps = $(info_TEXINFOS) doc/version.texi
DEFAULT_IGNORE_LIST = $(srcdir)/default-ignore-list DEFAULT_IGNORE_LIST = $(srcdir)/default-ignore-list
doc_deps = $(info_TEXINFOS) doc/version.texi $(DEFAULT_IGNORE_LIST)
TESTS_DIR = $(srcdir)/t TESTS_DIR = $(srcdir)/t
TESTS_OUT = tmp-testing-trees TESTS_OUT = tmp-testing-trees tmp-testing-trees-compat
TESTS_ENVIRONMENT = $(PERL) -Ibin -Ilib -I$(TESTS_DIR) TESTS_ENVIRONMENT = $(PERL) -Ibin -Ilib -I$(TESTS_DIR)
# This is a kind of hack; TESTS needs to be set to ensure that the # This is a kind of hack; TESTS needs to be set to ensure that the
@ -77,7 +77,7 @@ check_DATA = $(TESTS_OUT)
# Note that automake's `check' rule cannot be overridden # Note that automake's `check' rule cannot be overridden
# for some weird reason: # for some weird reason:
# #
# http://thread.gmane.org/gmane.comp.sysutils.automake.general/13040/focus=13041 # https://lists.gnu.org/archive/html/automake/2011-09/msg00029.html
# #
# so we override check-TESTS instead which is where the real work is # so we override check-TESTS instead which is where the real work is
# done anyway. Unfortunately this produces a warning with the -Wall # done anyway. Unfortunately this produces a warning with the -Wall
@ -87,6 +87,10 @@ check-TESTS:
dir=$(TESTS_DIR); \ dir=$(TESTS_DIR); \
$(TESTS_ENVIRONMENT) -MTest::Harness -e 'runtests(@ARGV)' "$${dir#./}"/*.t $(TESTS_ENVIRONMENT) -MTest::Harness -e 'runtests(@ARGV)' "$${dir#./}"/*.t
coverage:
PERL5OPT=-MDevel::Cover $(MAKE) check-TESTS
cover
$(TESTS_OUT): $(TESTS_OUT):
mkdir -p $@ mkdir -p $@
@ -95,7 +99,6 @@ EXTRA_DIST = \
bin/stow.in bin/chkstow.in lib/Stow.pm.in lib/Stow/Util.pm.in \ bin/stow.in bin/chkstow.in lib/Stow.pm.in lib/Stow/Util.pm.in \
doc/manual-split \ doc/manual-split \
$(TESTS) t/testutil.pm \ $(TESTS) t/testutil.pm \
$(TEXINFO_TEX) \
$(DEFAULT_IGNORE_LIST) \ $(DEFAULT_IGNORE_LIST) \
$(CPAN_FILES) $(CPAN_FILES)
CLEANFILES = $(bin_SCRIPTS) $(pm_DATA) $(pmstow_DATA) CLEANFILES = $(bin_SCRIPTS) $(pm_DATA) $(pmstow_DATA)
@ -193,7 +196,7 @@ doc/stow.8: bin/stow.in Makefile.am
# #
# If it were not for a troublesome dependency on doc/$(am__dirstamp): # If it were not for a troublesome dependency on doc/$(am__dirstamp):
# #
# http://article.gmane.org/gmane.comp.sysutils.automake.general/13192 # https://lists.gnu.org/archive/html/automake/2011-11/msg00107.html
# #
# we could have achieved this using the built-in rules combined with # we could have achieved this using the built-in rules combined with
# install-data-hook to rename from stow.pdf to manual.pdf etc. on # install-data-hook to rename from stow.pdf to manual.pdf etc. on
@ -302,3 +305,28 @@ ChangeLog: doc/ChangeLog.OLD
else \ else \
echo "Not in a git repository; can't update ChangeLog."; \ echo "Not in a git repository; can't update ChangeLog."; \
fi fi
# Watch for changes, and if any rebuilds are required, also do a
# make install.
#
# If we solved https://github.com/aspiers/stow/issues/84, we could
# probably ditch this:
watch:
@echo "Watching for changes to program source files ..."
@while true; do \
if $(MAKE) 2>&1 | \
grep -vE 'make\[[1-9]\]: (Entering|Leaving) directory ' | \
grep -v 'Nothing to be done'; \
then \
echo; \
echo "-----------------------------------------------------"; \
echo "make found things to rebuild; doing $(MAKE) install ..."; \
echo; \
$(MAKE) install; \
echo; \
echo ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"; \
echo; \
fi; \
sleep 1; \
done 2>&1 | \
grep -vE 'make\[[1-9]\]: (Entering|Leaving) directory '

82
NEWS
View file

@ -1,5 +1,81 @@
News file for Stow. News file for Stow.
* Changes in version 2.4.0
*** --dotfiles now works with directories
A long-standing bug preventing the --dotfiles option from working
correctly with directories has been fixed.
It should also works in combination with the --compat option.
*** Eliminated a spurious warning on unstowing
2.3.1 introduced a benign but annoying warning when unstowing
in certain circumstances. It looked like:
BUG in find_stowed_path? Absolute/relative mismatch between Stow dir X and path Y
This was caused by erroneous logic, and has now been fixed.
*** Unstowing logic has been improved in other cases
Several other improvements have been made internally to the
unstowing logic. These changes should all be either invisible
(except for changes to debug output) or improvements, but if you
encounter any unexpected behaviour, please report it as directed
in the manual.
*** Improved debug output
Extra output resulting from use of the -v / --verbose flag
now appears in a more logical and understandable way.
*** Janitorial tasks
Users are not substantially affected by these changes.
***** Added some more information from the web page to the README
***** Made some improvements to the documentation
***** Improve readability of source code
Quite a few extra details have been added in comments to clarify
how the code works. Many variable names have also been
improved. The comments of many Stow class methods have been
converted into Perl POD format.
***** Added a =CONTRIBUTING.md= file
***** Add a =watch= target to =Makefile=
=make watch= provides easy continual pre-processing during
development, which reduces the risk of debugging the wrong code.
***** Removed texinfo.tex from the distribution
This eliminates existing and future bit-rot.
***** Updated aclocal.m4 from 1.15.1 to 1.16.5
This mostly just updates copyright notices to 2021, and URLs to https.
***** Replace broken gmane links with links to lists.gnu.org
[[https://lars.ingebrigtsen.no/2020/01/06/whatever-happened-to-news-gmane-org/][gmane has been dead for quite a while.]]
***** Improve support for navigating / editing source via emacs
******* Support source navigation in emacs via [[https://github.com/jacktasia/dumb-jump][dumb-jump]].
******* Configure cperl-mode to match existing coding style.
*** Various maintainer tweaks
Further improved the release process and its documentation in
various minor ways.
* Changes in version 2.3.1 * Changes in version 2.3.1
*** Remove dependencies on Hash::Merge and Clone::Choose *** Remove dependencies on Hash::Merge and Clone::Choose
@ -138,6 +214,7 @@ News file for Stow.
consistency. consistency.
- INSTALL.md now also documents how to build directly from git. - INSTALL.md now also documents how to build directly from git.
*** Fixes for bugs, tests, and other technical debt *** Fixes for bugs, tests, and other technical debt
***** Add Docker files for convenient testing across multiple Perl versions ***** Add Docker files for convenient testing across multiple Perl versions
@ -235,7 +312,7 @@ due to Stow::Util missing $VERSION.
stow directory path being calculated as stow directory path being calculated as
../../../usr/home/user/local/stow relative to the target. ../../../usr/home/user/local/stow relative to the target.
See http://article.gmane.org/gmane.comp.gnu.stow.bugs/8820 for details. See https://lists.gnu.org/archive/html/bug-stow/2013-04/msg00000.html for details.
*** Fix stowing of relative links when --no-folding is used. *** Fix stowing of relative links when --no-folding is used.
@ -276,7 +353,7 @@ due to Stow::Util missing $VERSION.
Thanks to Gabriele Balducci for reporting this problem: Thanks to Gabriele Balducci for reporting this problem:
http://thread.gmane.org/gmane.comp.gnu.stow.general/6676 https://lists.gnu.org/archive/html/help-stow/2014-09/msg00000.html
*** Internal code cleanups *** Internal code cleanups
@ -586,4 +663,5 @@ due to Stow::Util missing $VERSION.
org-export-with-toc: nil org-export-with-toc: nil
org-export-with-author: nil org-export-with-author: nil
org-toc-odd-levels-only: t org-toc-odd-levels-only: t
org-blank-before-new-entry: ((heading . auto) (plain-list-item . auto))
End: End:

View file

@ -60,6 +60,56 @@ You can get the latest information about Stow from the home page:
http://www.gnu.org/software/stow/ http://www.gnu.org/software/stow/
Installation
------------
See [`INSTALL.md`](INSTALL.md) for installation instructions.
Documentation
-------------
Documentation for Stow is available
[online](https://www.gnu.org/software/stow/manual/), as is
[documentation for most GNU
software](https://www.gnu.org/software/manual/). Once you have Stow
installed, you may also find more information about Stow by running
`info stow` or `man stow`, or by looking at `/usr/share/doc/stow/`,
`/usr/local/doc/stow/`, or similar directories on your system. A
brief summary is available by running `stow --help`.
Mailing lists
-------------
Stow has the following mailing lists:
- [help-stow](https://lists.gnu.org/mailman/listinfo/help-stow) is for
general user help and discussion.
- [stow-devel](https://lists.gnu.org/mailman/listinfo/stow-devel) is
used to discuss most aspects of Stow, including development and
enhancement requests.
- [bug-stow](https://lists.gnu.org/mailman/listinfo/bug-stow) is for
bug reports.
Announcements about Stow are posted to
[info-stow](http://lists.gnu.org/mailman/listinfo/info-stow) and also,
as with most other GNU software, to
[info-gnu](http://lists.gnu.org/mailman/listinfo/info-gnu)
([archive](http://lists.gnu.org/archive/html/info-gnu/)).
Security reports that should not be made immediately public can be
sent directly to the maintainer. If there is no response to an urgent
issue, you can escalate to the general
[security](http://lists.gnu.org/mailman/listinfo/security) mailing
list for advice.
The Savannah project also has a [mailing
lists](https://savannah.gnu.org/mail/?group=stow) page.
Getting involved
----------------
Please see the [`CONTRIBUTING.md` file](CONTRIBUTING.md).
License License
------- -------
@ -71,18 +121,6 @@ are permitted in any medium without royalty provided the copyright
notice and this notice are preserved. This file is offered as-is, notice and this notice are preserved. This file is offered as-is,
without any warranty. without any warranty.
Installation
------------
See [`INSTALL.md`](INSTALL.md) for installation instructions.
Feedback
--------
Please do send comments, questions, and constructive criticism. The
mailing lists and any other communication channels are detailed on the
above home page.
Brief history and authorship Brief history and authorship
---------------------------- ----------------------------

2
TODO
View file

@ -4,7 +4,7 @@
install-info, amongst other things: install-info, amongst other things:
*** http://unix.stackexchange.com/questions/73426/dealing-with-gnu-stow-conflicts *** http://unix.stackexchange.com/questions/73426/dealing-with-gnu-stow-conflicts
*** http://article.gmane.org/gmane.comp.gnu.stow.general/6661 *** https://lists.gnu.org/archive/html/help-stow/2013-04/msg00016.html
* Get permission for next documentation release to be under FDL 1.3 * Get permission for next documentation release to be under FDL 1.3

4
aclocal.m4 vendored
View file

@ -14,8 +14,8 @@
m4_ifndef([AC_CONFIG_MACRO_DIRS], [m4_defun([_AM_CONFIG_MACRO_DIRS], [])m4_defun([AC_CONFIG_MACRO_DIRS], [_AM_CONFIG_MACRO_DIRS($@)])]) m4_ifndef([AC_CONFIG_MACRO_DIRS], [m4_defun([_AM_CONFIG_MACRO_DIRS], [])m4_defun([AC_CONFIG_MACRO_DIRS], [_AM_CONFIG_MACRO_DIRS($@)])])
m4_ifndef([AC_AUTOCONF_VERSION], m4_ifndef([AC_AUTOCONF_VERSION],
[m4_copy([m4_PACKAGE_VERSION], [AC_AUTOCONF_VERSION])])dnl [m4_copy([m4_PACKAGE_VERSION], [AC_AUTOCONF_VERSION])])dnl
m4_if(m4_defn([AC_AUTOCONF_VERSION]), [2.71],, m4_if(m4_defn([AC_AUTOCONF_VERSION]), [2.72],,
[m4_warning([this file was generated for autoconf 2.71. [m4_warning([this file was generated for autoconf 2.72.
You have another version of autoconf. It may work, but is not guaranteed to. You have another version of autoconf. It may work, but is not guaranteed to.
If you have problems, you may need to regenerate the build system entirely. If you have problems, you may need to regenerate the build system entirely.
To do so, use the procedure documented by the package, typically 'autoreconf'.])]) To do so, use the procedure documented by the package, typically 'autoreconf'.])])

1
automake/.gitignore vendored
View file

@ -2,3 +2,4 @@ install-sh
missing missing
mdate-sh mdate-sh
test-driver test-driver
texinfo.tex

View file

@ -123,6 +123,5 @@ sub list {
# Local variables: # Local variables:
# mode: perl # mode: perl
# cperl-indent-level: 4
# End: # End:
# vim: ft=perl # vim: ft=perl

View file

@ -474,7 +474,6 @@ sub main {
my ($options, $pkgs_to_unstow, $pkgs_to_stow) = process_options(); my ($options, $pkgs_to_unstow, $pkgs_to_stow) = process_options();
my $stow = new Stow(%$options); my $stow = new Stow(%$options);
# current dir is now the target directory
$stow->plan_unstow(@$pkgs_to_unstow); $stow->plan_unstow(@$pkgs_to_unstow);
$stow->plan_stow (@$pkgs_to_stow); $stow->plan_stow (@$pkgs_to_stow);
@ -849,6 +848,5 @@ sub version {
# Local variables: # Local variables:
# mode: perl # mode: perl
# cperl-indent-level: 4
# end: # end:
# vim: ft=perl # vim: ft=perl

View file

@ -15,11 +15,11 @@ dnl along with this program. If not, see https://www.gnu.org/licenses/.
dnl Process this file with Autoconf to produce configure dnl dnl Process this file with Autoconf to produce configure dnl
AC_INIT([stow], [2.3.2-fixbug56727], [bug-stow@gnu.org]) AC_INIT([stow], [2.4.0], [bug-stow@gnu.org])
AC_PREREQ([2.61]) AC_PREREQ([2.61])
AC_CONFIG_AUX_DIR([automake]) AC_CONFIG_AUX_DIR([automake])
# Unfortunately we have to disable warnings for overrides, because we # Unfortunately we have to disable warnings for overrides, because we
# need to override the built-in `check' rule and also the TEXI2DVI # need to override the built-in `check-TESTS' rule and also the TEXI2DVI
# variable. # variable.
AM_INIT_AUTOMAKE([-Wall -Werror -Wno-override dist-bzip2 foreign]) AM_INIT_AUTOMAKE([-Wall -Werror -Wno-override dist-bzip2 foreign])
AC_PROG_INSTALL AC_PROG_INSTALL

View file

@ -13,6 +13,7 @@ _darcs
\.git \.git
\.gitignore \.gitignore
\.gitmodules
.+~ # emacs backup files .+~ # emacs backup files
\#.*\# # emacs autosave files \#.*\# # emacs autosave files

View file

@ -21,17 +21,16 @@ Release procedure
version=$( tools/get-version ) && echo $version version=$( tools/get-version ) && echo $version
- Ensure NEWS contains the latest changes, and that any new - Ensure NEWS contains the latest changes. If necessary, commit
contributors have been added to THANKS. If necessary, commit
any additions: any additions:
git commit -m "Prepare NEWS and THANKS for $version release" git commit -m "Prepare NEWS for $version release"
- Check CPAN distribution will work via Module::Build: - Check CPAN distribution will work via Module::Build:
- Start from a clean slate: - Start from a clean slate:
make distclean make maintainer-clean
autoreconf -iv autoreconf -iv
- Generate stow, chkstow, and lib/Stow.pm via: - Generate stow, chkstow, and lib/Stow.pm via:

View file

@ -19,13 +19,13 @@ This manual describes GNU Stow version @value{VERSION}
Software and documentation is copyrighted by the following: Software and documentation is copyrighted by the following:
@copyright{} 1993, 1994, 1995, 1996 Bob Glickstein <bobg+stow@@zanshin.com> @copyright{} 1993, 1994, 1995, 1996 Bob Glickstein @email{bobg+stow@@zanshin.com}
@* @*
@copyright{} 2000, 2001 Guillaume Morin <gmorin@@gnu.org> @copyright{} 2000, 2001 Guillaume Morin @email{gmorin@@gnu.org}
@* @*
@copyright{} 2007 Kahlil (Kal) Hodgson <kahlil@@internode.on.net> @copyright{} 2007 Kahlil (Kal) Hodgson @email{kahlil@@internode.on.net}
@* @*
@copyright{} 2011 Adam Spiers <stow@@adamspiers.org> @copyright{} 2011 Adam Spiers @email{stow@@adamspiers.org}
@quotation @quotation
Permission is granted to make and distribute verbatim copies of this Permission is granted to make and distribute verbatim copies of this
@ -99,7 +99,7 @@ appear to be installed in a single directory tree.
* Multiple Stow Directories:: Further segregating software. * Multiple Stow Directories:: Further segregating software.
* Target Maintenance:: Cleaning up mistakes. * Target Maintenance:: Cleaning up mistakes.
* Resource Files:: Setting default command line options. * Resource Files:: Setting default command line options.
* Compile-time vs Install-time:: Faking out `make install'. * Compile-time vs. Install-time:: Faking out `make install'.
* Bootstrapping:: When stow and perl are not yet stowed. * Bootstrapping:: When stow and perl are not yet stowed.
* Reporting Bugs:: How, what, where, and when to report. * Reporting Bugs:: How, what, where, and when to report.
* Known Bugs:: Don't report any of these. * Known Bugs:: Don't report any of these.
@ -220,9 +220,12 @@ to be installed in a particular directory structure --- e.g., with
@cindex target directory @cindex target directory
A @dfn{target directory} is the root of a tree in which one or more A @dfn{target directory} is the root of a tree in which one or more
packages wish to @emph{appear} to be installed. A common, but by no packages wish to @emph{appear} to be installed. @file{/usr/local} is a
means the only such location is @file{/usr/local}. The examples in this common choice for this, but by no means the only such location. Another
manual will use @file{/usr/local} as the target directory. common choice is @file{~} (i.e.@: the user's @code{$HOME} directory) in
the case where Stow is being used to manage the user's configuration
(``dotfiles'') and other files in their @code{$HOME}. The examples in
this manual will use @file{/usr/local} as the target directory.
@cindex stow directory @cindex stow directory
A @dfn{stow directory} is the root of a tree containing separate A @dfn{stow directory} is the root of a tree containing separate
@ -240,6 +243,11 @@ installation image for Perl includes: a @file{bin} directory containing
containing Texinfo documentation; a @file{lib/perl} directory containing containing Texinfo documentation; a @file{lib/perl} directory containing
Perl libraries; and a @file{man/man1} directory containing man pages. Perl libraries; and a @file{man/man1} directory containing man pages.
@quotation Note
This is a @emph{pre-}installation image which exists even before Stow
has installed any symlinks into the target directory which point to it.
@end quotation
@cindex package directory @cindex package directory
@cindex package name @cindex package name
A @dfn{package directory} is the root of a tree containing the A @dfn{package directory} is the root of a tree containing the
@ -255,15 +263,68 @@ target directory, @file{/usr/local/stow} is the stow directory,
@file{/usr/local/stow/perl} is the package directory, and @file{/usr/local/stow/perl} is the package directory, and
@file{bin/perl} within is part of the installation image. @file{bin/perl} within is part of the installation image.
@anchor{symlink}
@cindex symlink @cindex symlink
@cindex symlink source
@cindex symlink destination
@cindex relative symlink @cindex relative symlink
@cindex absolute symlink @cindex absolute symlink
A @dfn{symlink} is a symbolic link. A symlink can be @dfn{relative} or A @dfn{symlink} is a symbolic link, i.e.@: an entry on the filesystem
@dfn{absolute}. An absolute symlink names a full path; that is, one whose path is sometimes called the @dfn{symlink source}, which points to
starting from @file{/}. A relative symlink names a relative path; that another location on the filesystem called the @dfn{symlink destination}.
is, one not starting from @file{/}. The target of a relative symlink is There is no guarantee that the destination actually exists.
computed starting from the symlink's own directory. Stow only
creates relative symlinks. In general, symlinks can be @dfn{relative} or @dfn{absolute}. A symlink
is absolute when the destination names a full path; that is, one
starting from @file{/}. A symlink is relative when the destination
names a relative path; that is, one not starting from @file{/}. The
destination of a relative symlink is computed starting from the
symlink's own directory, i.e.@: the directory containing the symlink
source.
@quotation Note
Stow only creates symlinks within the target directory which point to
locations @emph{outside} the target directory and inside the stow
directory.
Consequently, we avoid referring to symlink destinations as symlink
@emph{targets}, since this would result in the word ``target'' having
two different meanings:
@enumerate
@item
the target directory, i.e.@: the directory into which Stow targets
installation, where symlinks are managed by Stow, and
@item
the destinations of those symlinks.
@end enumerate
If we did not avoid the second meaning of ``target'', then it would lead
to confusing language, such as describing Stow as installing symlinks
into the target directory which point to targets @emph{outside} the
target directory.
Similarly, the word ``source'' can have two different meanings in this
context:
@enumerate
@item
the installation image, or some of its contents, and
@item
the location of symlinks (the ``source'' of the link, vs.@: its
destination).
@end enumerate
Therefore it should also be avoided, or at least care taken to ensure
that the meaning is not ambiguous.
@end quotation
@c =========================================================================== @c ===========================================================================
@node Invoking Stow, Ignore Lists, Terminology, Top @node Invoking Stow, Ignore Lists, Terminology, Top
@ -383,7 +444,7 @@ refolding (@pxref{tree refolding}). If a new subdirectory is
encountered whilst stowing a new package, the subdirectory is created encountered whilst stowing a new package, the subdirectory is created
within the target, and its contents are symlinked, rather than just within the target, and its contents are symlinked, rather than just
creating a symlink for the directory. If removal of symlinks whilst creating a symlink for the directory. If removal of symlinks whilst
unstowing a package causes a subtree to be foldable (i.e. only unstowing a package causes a subtree to be foldable (i.e.@: only
containing symlinks to a single package), that subtree will not be containing symlinks to a single package), that subtree will not be
removed and replaced with a symlink. removed and replaced with a symlink.
@ -428,13 +489,15 @@ doing. Verbosity levels are from 0 to 5; 0 is the default. Using
@item -p @item -p
@itemx --compat @itemx --compat
Scan the whole target tree when unstowing. By default, only Scan the whole target tree when unstowing. By default, only directories
directories specified in the @dfn{installation image} are scanned specified in the @dfn{installation image} are scanned during an unstow
during an unstow operation. Scanning the whole tree can be operation. Previously Stow scanned the whole tree, which can be
prohibitive if your target tree is very large. This option restores prohibitive if your target tree is very large, but on the other hand has
the legacy behaviour; however, the @option{--badlinks} option to the the advantage of unstowing previously stowed links which are no longer
@command{chkstow} utility may be a better way of ensuring that your present in the installation image and therefore orphaned. This option
installation does not have any dangling symlinks (@pxref{Target restores the legacy behaviour; however, the @option{--badlinks} option
to the @command{chkstow} utility may be a better way of ensuring that
your installation does not have any dangling symlinks (@pxref{Target
Maintenance}). Maintenance}).
@item -V @item -V
@ -813,7 +876,7 @@ This is much faster and cleaner than performing two separate
invocations of stow, because redundant folding/unfolding operations invocations of stow, because redundant folding/unfolding operations
can be factored out. In addition, all the operations are calculated can be factored out. In addition, all the operations are calculated
and merged before being executed (@pxref{Deferred Operation}), so the and merged before being executed (@pxref{Deferred Operation}), so the
amount of of time in which GNU Emacs is unavailable is minimised. amount of time in which GNU Emacs is unavailable is minimised.
You can mix and match any number of actions, for example, You can mix and match any number of actions, for example,
@ -893,7 +956,7 @@ directory.
@end table @end table
@c =========================================================================== @c ===========================================================================
@node Resource Files, Compile-time vs Install-time, Target Maintenance, Top @node Resource Files, Compile-time vs. Install-time, Target Maintenance, Top
@chapter Resource Files @chapter Resource Files
@cindex resource files @cindex resource files
@cindex configuration files @cindex configuration files
@ -960,8 +1023,8 @@ resource files. This is also true of any package names given in the
resource file. resource file.
@c =========================================================================== @c ===========================================================================
@node Compile-time vs Install-time, Bootstrapping, Resource Files, Top @node Compile-time vs. Install-time, Bootstrapping, Resource Files, Top
@chapter Compile-time vs Install-time @chapter Compile-time vs. Install-time
Software whose installation is managed with Stow needs to be installed Software whose installation is managed with Stow needs to be installed
in one place (the package directory, e.g. @file{/usr/local/stow/perl}) in one place (the package directory, e.g. @file{/usr/local/stow/perl})
@ -1043,7 +1106,7 @@ following sections.
@end menu @end menu
@c --------------------------------------------------------------------------- @c ---------------------------------------------------------------------------
@node GNU Emacs, Other FSF Software, Compile-time vs Install-time, Compile-time vs Install-time @node GNU Emacs, Other FSF Software, Compile-time vs. Install-time, Compile-time vs. Install-time
@section GNU Emacs @section GNU Emacs
Although the Free Software Foundation has many enlightened practices Although the Free Software Foundation has many enlightened practices
@ -1076,7 +1139,7 @@ make do-install prefix=/usr/local/stow/emacs
@end example @end example
@c --------------------------------------------------------------------------- @c ---------------------------------------------------------------------------
@node Other FSF Software, Cygnus Software, GNU Emacs, Compile-time vs Install-time @node Other FSF Software, Cygnus Software, GNU Emacs, Compile-time vs. Install-time
@section Other FSF Software @section Other FSF Software
The Free Software Foundation, the organization behind the GNU project, The Free Software Foundation, the organization behind the GNU project,
@ -1097,7 +1160,7 @@ and @samp{make install} steps to work correctly without needing to
``fool'' the build process. ``fool'' the build process.
@c --------------------------------------------------------------------------- @c ---------------------------------------------------------------------------
@node Cygnus Software, Perl and Perl 5 Modules, Other FSF Software, Compile-time vs Install-time @node Cygnus Software, Perl and Perl 5 Modules, Other FSF Software, Compile-time vs. Install-time
@section Cygnus Software @section Cygnus Software
Cygnus is a commercial supplier and supporter of GNU software. It has Cygnus is a commercial supplier and supporter of GNU software. It has
@ -1126,7 +1189,7 @@ is recompiling files. Usually it will work just fine; otherwise,
install manually. install manually.
@c --------------------------------------------------------------------------- @c ---------------------------------------------------------------------------
@node Perl and Perl 5 Modules, , Cygnus Software, Compile-time vs Install-time @node Perl and Perl 5 Modules, , Cygnus Software, Compile-time vs. Install-time
@section Perl and Perl 5 Modules @section Perl and Perl 5 Modules
Perl 4.036 allows you to specify different locations for installation Perl 4.036 allows you to specify different locations for installation
@ -1229,7 +1292,7 @@ find cpan.* \( -name .exists -o -name perllocal.pod \) -print | \
@c --------------------------------------------------------------------------- @c ---------------------------------------------------------------------------
@node Bootstrapping, Reporting Bugs, Compile-time vs Install-time, Top @node Bootstrapping, Reporting Bugs, Compile-time vs. Install-time, Top
@chapter Bootstrapping @chapter Bootstrapping
Suppose you have a stow directory all set up and ready to go: Suppose you have a stow directory all set up and ready to go:
@ -1264,9 +1327,32 @@ perl/bin/perl stow/bin/stow -vv *
@node Reporting Bugs, Known Bugs, Bootstrapping, Top @node Reporting Bugs, Known Bugs, Bootstrapping, Top
@chapter Reporting Bugs @chapter Reporting Bugs
Please send bug reports to the current maintainers by electronic You can report bugs to the current maintainers in one of three ways:
mail. The address to use is @samp{<bug-stow@@gnu.org>}. Please
include: @enumerate
@item
Send e-mail to @email{bug-stow@@gnu.org}.
@item
File an issue in @uref{https://savannah.gnu.org/bugs/?group=stow,
the Savannah bug tracker}.
@item
File an issue in
@uref{https://github.com/aspiers/stow/issues/, the GitHub project}.
@end enumerate
While GitHub is arguably the most convenient of these three options, it
@uref{https://www.gnu.org/software/repo-criteria-evaluation.html#GitHub,
is not the most ethical or freedom-preserving way to host software
projects}. Therefore the GitHub project may be
@uref{https://github.com/aspiers/stow/issues/43, moved to a more ethical
hosting service} in the future.
Before reporting a bug, it is recommended to check whether it is already
known, so please first @pxref{Known Bugs}.
When reporting a new bug, please include:
@itemize @bullet @itemize @bullet
@item @item
@ -1287,12 +1373,13 @@ the precise command you gave;
@item @item
the output from the command (preferably verbose output, obtained by the output from the command (preferably verbose output, obtained by
adding @samp{--verbose=3} to the Stow command line). adding @samp{--verbose=5} to the Stow command line).
@end itemize @end itemize
If you are really keen, consider developing a minimal test case and If you are really keen, consider developing a minimal test case and
creating a new test. See the @file{t/} directory in the source for creating a new test. See the @file{t/} directory in the source for lots
lots of examples. of examples, and the @file{CONTRIBUTING.md} file for a guide on how to
contribute.
Before reporting a bug, please read the manual carefully, especially Before reporting a bug, please read the manual carefully, especially
@ref{Known Bugs}, to see whether you're encountering @ref{Known Bugs}, to see whether you're encountering
@ -1303,13 +1390,22 @@ something that doesn't need reporting.
@node Known Bugs, GNU General Public License, Reporting Bugs, Top @node Known Bugs, GNU General Public License, Reporting Bugs, Top
@chapter Known Bugs @chapter Known Bugs
There are no known bugs in Stow version @value{VERSION}! Known bugs can be found in the following locations:
If you think you have found one, please @pxref{Reporting Bugs}.
@c @itemize @bullet @itemize
@c @item @item
@c Put known bugs here @uref{https://github.com/aspiers/stow/issues/, the GitHub issue tracker}
@c @end itemize
@item
@uref{https://savannah.gnu.org/bugs/?group=stow, the Savannah bug
tracker}
@item
the @uref{https://lists.gnu.org/archive/html/bug-stow/, bug-stow list
archives}
@end itemize
If you think you have found a new bug, please @pxref{Reporting Bugs}.
@c =========================================================================== @c ===========================================================================
@node GNU General Public License, Index, Known Bugs, Top @node GNU General Public License, Index, Known Bugs, Top

File diff suppressed because it is too large Load diff

View file

@ -16,10 +16,9 @@
# Build docker image: `docker build -t stowtest` # Build docker image: `docker build -t stowtest`
# Run tests: (from stow src directory) # Run tests: (from stow src directory)
# `docker run --rm -it -v $(pwd):$(pwd) -w $(pwd) stowtest` # `docker run --rm -it -v $(pwd):$(pwd) -w $(pwd) stowtest`
FROM debian:jessie FROM debian:bookworm
RUN printf "deb http://archive.debian.org/debian/ jessie main\ndeb-src http://archive.debian.org/debian/ jessie main\ndeb http://security.debian.org jessie/updates main\ndeb-src http://security.debian.org jessie/updates main" > /etc/apt/sources.list RUN DEBIAN_FRONTEND=noninteractive apt-get update -qq
RUN DEBIAN_FRONTEND=noninteractive \ RUN DEBIAN_FRONTEND=noninteractive \
apt-get update -qq && \
apt-get install -y -q \ apt-get install -y -q \
autoconf \ autoconf \
bzip2 \ bzip2 \

File diff suppressed because it is too large Load diff

View file

@ -32,12 +32,14 @@ Supporting utility routines for L<Stow>.
use strict; use strict;
use warnings; use warnings;
use File::Spec;
use POSIX qw(getcwd); use POSIX qw(getcwd);
use base qw(Exporter); use base qw(Exporter);
our @EXPORT_OK = qw( our @EXPORT_OK = qw(
error debug set_debug_level set_test_mode error debug set_debug_level set_test_mode
join_paths parent canon_path restore_cwd adjust_dotfile join_paths parent canon_path restore_cwd
adjust_dotfile unadjust_dotfile
); );
our $ProgramName = 'stow'; our $ProgramName = 'stow';
@ -93,7 +95,7 @@ sub set_test_mode {
} }
} }
=head2 debug($level, $msg) =head2 debug($level[, $indent_level], $msg)
Logs to STDERR based on C<$debug_level> setting. C<$level> is the Logs to STDERR based on C<$debug_level> setting. C<$level> is the
minimum verbosity level required to output C<$msg>. All output is to minimum verbosity level required to output C<$msg>. All output is to
@ -125,13 +127,18 @@ overriding, fixing invalid links
=cut =cut
sub debug { sub debug {
my ($level, $msg) = @_; my $level = shift;
my $indent_level;
# Maintain backwards-compatibility in case anyone's relying on this.
$indent_level = $_[0] =~ /^\d+$/ ? shift : 0;
my $msg = shift;
if ($debug_level >= $level) { if ($debug_level >= $level) {
my $indent = ' ' x $indent_level;
if ($test_mode) { if ($test_mode) {
print "# $msg\n"; print "# $indent$msg\n";
} }
else { else {
warn "$msg\n"; warn "$indent$msg\n";
} }
} }
} }
@ -142,29 +149,53 @@ sub debug {
# Parameters: path1, path2, ... => paths # Parameters: path1, path2, ... => paths
# Returns : concatenation of given paths # Returns : concatenation of given paths
# Throws : n/a # Throws : n/a
# Comments : factors out redundant path elements: # Comments : Factors out some redundant path elements:
# : '//' => '/' and 'a/b/../c' => 'a/c' # : '//' => '/', and 'a/b/../c' => 'a/c'. We need this function
# : with this behaviour, even though b could be a symlink to
# : elsewhere, as noted in the perldoc for File::Spec->canonpath().
# : This behaviour is deliberately different to
# : Stow::Util::canon_path(), because the way join_paths() is used
# : relies on this. Firstly, there is no guarantee that the paths
# : exist, so a filesystem check is inappropriate.
# :
# : For example, it's used to determine the path from the target
# : directory to a symlink destination. So if a symlink
# : path/to/target/a/b/c points to ../../../stow/pkg/a/b/c,
# : then joining path/to/target/a/b with ../../../stow/pkg/a/b/c
# : yields path/to/stow/pkg/a/b/c, and it's crucial that the
# : path/to/stow prefix matches a recognisable stow directory.
#============================================================================ #============================================================================
sub join_paths { sub join_paths {
my @paths = @_; my @paths = @_;
# weed out empty components and concatenate debug(5, 5, "| Joining: @paths");
my $result = join '/', grep {! /\A\z/} @paths; my $result = '';
for my $part (@paths) {
next if ! length $part; # probably shouldn't happen?
$part = File::Spec->canonpath($part);
# factor out back references and remove redundant /'s) if (substr($part, 0, 1) eq '/') {
my @result = (); $result = $part; # absolute path, so ignore all previous parts
PART:
for my $part (split m{/+}, $result) {
next PART if $part eq '.';
if (@result && $part eq '..' && $result[-1] ne '..') {
pop @result;
} }
else { else {
push @result, $part; $result .= '/' if length $result && $result ne '/';
$result .= $part;
} }
debug(7, 6, "| Join now: $result");
} }
debug(6, 5, "| Joined: $result");
return join '/', @result; # Need this to remove any initial ./
$result = File::Spec->canonpath($result);
# remove foo/..
1 while $result =~ s,(^|/)(?!\.\.)[^/]+/\.\.(/|$),$1,;
debug(6, 5, "| After .. removal: $result");
$result = File::Spec->canonpath($result);
debug(5, 5, "| Final join: $result");
return $result;
} }
#===== METHOD =============================================================== #===== METHOD ===============================================================
@ -209,17 +240,17 @@ sub restore_cwd {
} }
sub adjust_dotfile { sub adjust_dotfile {
my ($target) = @_; my ($pkg_node) = @_;
(my $adjusted = $pkg_node) =~ s/^dot-([^.])/.$1/;
return $adjusted;
}
my @result = (); # Needed when unstowing with --compat and --dotfiles
for my $part (split m{/+}, $target) { sub unadjust_dotfile {
if (($part ne "dot-") && ($part ne "dot-.")) { my ($target_node) = @_;
$part =~ s/^dot-/./; return $target_node if $target_node =~ /^\.\.?$/;
} (my $adjusted = $target_node) =~ s/^\./dot-/;
push @result, $part; return $adjusted;
}
return join '/', @result;
} }
=head1 BUGS =head1 BUGS
@ -232,6 +263,5 @@ sub adjust_dotfile {
# Local variables: # Local variables:
# mode: perl # mode: perl
# cperl-indent-level: 4
# end: # end:
# vim: ft=perl # vim: ft=perl

View file

@ -22,10 +22,11 @@
use strict; use strict;
use warnings; use warnings;
use Test::More tests => 6; use Test::More tests => 4;
use English qw(-no_match_vars); use English qw(-no_match_vars);
use testutil; use testutil;
use Stow::Util;
init_test_dirs(); init_test_dirs();
cd("$TEST_DIR/target"); cd("$TEST_DIR/target");
@ -34,48 +35,64 @@ my $stow;
# Note that each of the following tests use a distinct set of files # Note that each of the following tests use a distinct set of files
# subtest('nothing to clean in a simple tree' => sub {
# nothing to clean in a simple tree plan tests => 1;
#
make_path('../stow/pkg1/bin1');
make_file('../stow/pkg1/bin1/file1');
make_link('bin1', '../stow/pkg1/bin1');
make_path('../stow/pkg1/bin1'); $stow = new_Stow();
make_file('../stow/pkg1/bin1/file1'); $stow->cleanup_invalid_links('./');
make_link('bin1', '../stow/pkg1/bin1'); is(
$stow = new_Stow();
$stow->cleanup_invalid_links('./');
is(
scalar($stow->get_tasks), 0 scalar($stow->get_tasks), 0
=> 'nothing to clean' => 'nothing to clean'
); );
});
# subtest('cleanup an orphaned owned link in a simple tree' => sub {
# cleanup a bad link in a simple tree plan tests => 3;
#
make_path('bin2');
make_path('../stow/pkg2/bin2');
make_file('../stow/pkg2/bin2/file2a');
make_link('bin2/file2a', '../../stow/pkg2/bin2/file2a');
make_invalid_link('bin2/file2b', '../../stow/pkg2/bin2/file2b');
$stow = new_Stow(); make_path('bin2');
$stow->cleanup_invalid_links('bin2'); make_path('../stow/pkg2/bin2');
is($stow->get_conflict_count, 0, 'no conflicts cleaning up bad link'); make_file('../stow/pkg2/bin2/file2a');
is(scalar($stow->get_tasks), 1, 'one task cleaning up bad link'); make_link('bin2/file2a', '../../stow/pkg2/bin2/file2a');
is($stow->link_task_action('bin2/file2b'), 'remove', 'removal task for bad link'); make_invalid_link('bin2/file2b', '../../stow/pkg2/bin2/file2b');
# $stow = new_Stow();
# dont cleanup a bad link not owned by stow $stow->cleanup_invalid_links('bin2');
# is($stow->get_conflict_count, 0, 'no conflicts cleaning up bad link');
is(scalar($stow->get_tasks), 1, 'one task cleaning up bad link');
is($stow->link_task_action('bin2/file2b'), 'remove', 'removal task for bad link');
});
make_path('bin3'); subtest("don't cleanup a bad link not owned by stow" => sub {
make_path('../stow/pkg3/bin3'); plan tests => 2;
make_file('../stow/pkg3/bin3/file3a');
make_link('bin3/file3a', '../../stow/pkg3/bin3/file3a');
make_invalid_link('bin3/file3b', '../../empty');
$stow = new_Stow(); make_path('bin3');
$stow->cleanup_invalid_links('bin3'); make_path('../stow/pkg3/bin3');
is($stow->get_conflict_count, 0, 'no conflicts cleaning up bad link not owned by stow'); make_file('../stow/pkg3/bin3/file3a');
is(scalar($stow->get_tasks), 0, 'no tasks cleaning up bad link not owned by stow'); make_link('bin3/file3a', '../../stow/pkg3/bin3/file3a');
make_invalid_link('bin3/file3b', '../../empty');
$stow = new_Stow();
$stow->cleanup_invalid_links('bin3');
is($stow->get_conflict_count, 0, 'no conflicts cleaning up bad link not owned by stow');
is(scalar($stow->get_tasks), 0, 'no tasks cleaning up bad link not owned by stow');
});
subtest("don't cleanup a valid link in the target not owned by stow" => sub {
plan tests => 2;
make_path('bin4');
make_path('../stow/pkg4/bin4');
make_file('../stow/pkg4/bin4/file3a');
make_link('bin4/file3a', '../../stow/pkg4/bin4/file3a');
make_file("unowned");
make_link('bin4/file3b', '../unowned');
$stow = new_Stow();
$stow->cleanup_invalid_links('bin4');
is($stow->get_conflict_count, 0, 'no conflicts cleaning up bad link not owned by stow');
is(scalar($stow->get_tasks), 0, 'no tasks cleaning up bad link not owned by stow');
});

View file

@ -22,190 +22,214 @@
use strict; use strict;
use warnings; use warnings;
use testutil; use Test::More tests => 12;
use Test::More tests => 10;
use English qw(-no_match_vars); use English qw(-no_match_vars);
use Stow::Util qw(adjust_dotfile unadjust_dotfile);
use testutil; use testutil;
init_test_dirs(); init_test_dirs();
cd("$TEST_DIR/target"); cd("$TEST_DIR/target");
subtest('adjust_dotfile()', sub {
plan tests => 4;
my @TESTS = (
['file'],
['dot-'],
['dot-.'],
['dot-file', '.file'],
);
for my $test (@TESTS) {
my ($input, $expected) = @$test;
$expected ||= $input;
is(adjust_dotfile($input), $expected);
}
});
subtest('unadjust_dotfile()', sub {
plan tests => 4;
my @TESTS = (
['file'],
['.'],
['..'],
['.file', 'dot-file'],
);
for my $test (@TESTS) {
my ($input, $expected) = @$test;
$expected ||= $input;
is(unadjust_dotfile($input), $expected);
}
});
my $stow; my $stow;
# subtest("stow dot-foo as .foo", sub {
# process a dotfile marked with 'dot' prefix plan tests => 1;
# $stow = new_Stow(dir => '../stow', dotfiles => 1);
make_path('../stow/dotfiles');
make_file('../stow/dotfiles/dot-foo');
$stow = new_Stow(dir => '../stow', dotfiles => 1); $stow->plan_stow('dotfiles');
$stow->process_tasks();
make_path('../stow/dotfiles'); is(
make_file('../stow/dotfiles/dot-foo');
$stow->plan_stow('dotfiles');
$stow->process_tasks();
is(
readlink('.foo'), readlink('.foo'),
'../stow/dotfiles/dot-foo', '../stow/dotfiles/dot-foo',
=> 'processed dotfile' => 'processed dotfile'
); );
});
# subtest("stow dot-foo as dot-foo without --dotfile enabled", sub {
# ensure that turning off dotfile processing links files as usual plan tests => 1;
# $stow = new_Stow(dir => '../stow', dotfiles => 0);
make_path('../stow/dotfiles');
make_file('../stow/dotfiles/dot-foo');
$stow = new_Stow(dir => '../stow', dotfiles => 0); $stow->plan_stow('dotfiles');
$stow->process_tasks();
make_path('../stow/dotfiles'); is(
make_file('../stow/dotfiles/dot-foo');
$stow->plan_stow('dotfiles');
$stow->process_tasks();
is(
readlink('dot-foo'), readlink('dot-foo'),
'../stow/dotfiles/dot-foo', '../stow/dotfiles/dot-foo',
=> 'unprocessed dotfile' => 'unprocessed dotfile'
); );
});
subtest("stow dot-emacs dir as .emacs", sub {
plan tests => 1;
$stow = new_Stow(dir => '../stow', dotfiles => 1);
# make_path('../stow/dotfiles/dot-emacs');
# process folder marked with 'dot' prefix make_file('../stow/dotfiles/dot-emacs/init.el');
#
$stow = new_Stow(dir => '../stow', dotfiles => 1); $stow->plan_stow('dotfiles');
$stow->process_tasks();
make_path('../stow/dotfiles/dot-emacs'); is(
make_file('../stow/dotfiles/dot-emacs/init.el');
$stow->plan_stow('dotfiles');
$stow->process_tasks();
is(
readlink('.emacs'), readlink('.emacs'),
'../stow/dotfiles/dot-emacs', '../stow/dotfiles/dot-emacs',
=> 'processed dotfile folder' => 'processed dotfile dir'
); );
});
# subtest("stow dir marked with 'dot' prefix when directory exists in target", sub {
# process folder marked with 'dot' prefix plan tests => 1;
# when directory exists is target $stow = new_Stow(dir => '../stow', dotfiles => 1);
#
$stow = new_Stow(dir => '../stow', dotfiles => 1); make_path('../stow/dotfiles/dot-emacs.d');
make_file('../stow/dotfiles/dot-emacs.d/init.el');
make_path('.emacs.d');
make_path('../stow/dotfiles/dot-emacs.d'); $stow->plan_stow('dotfiles');
make_file('../stow/dotfiles/dot-emacs.d/init.el'); $stow->process_tasks();
make_path('.emacs.d'); is(
$stow->plan_stow('dotfiles');
$stow->process_tasks();
is(
readlink('.emacs.d/init.el'), readlink('.emacs.d/init.el'),
'../../stow/dotfiles/dot-emacs.d/init.el', '../../stow/dotfiles/dot-emacs.d/init.el',
=> 'processed dotfile folder when folder exists (1 level)' => 'processed dotfile dir when dir exists (1 level)'
); );
});
# subtest("stow dir marked with 'dot' prefix when directory exists in target (2 levels)", sub {
# process folder marked with 'dot' prefix plan tests => 1;
# when directory exists is target (2 levels) $stow = new_Stow(dir => '../stow', dotfiles => 1);
#
$stow = new_Stow(dir => '../stow', dotfiles => 1); make_path('../stow/dotfiles/dot-emacs.d/dot-emacs.d');
make_file('../stow/dotfiles/dot-emacs.d/dot-emacs.d/init.el');
make_path('.emacs.d');
make_path('../stow/dotfiles/dot-emacs.d/dot-emacs.d'); $stow->plan_stow('dotfiles');
make_file('../stow/dotfiles/dot-emacs.d/dot-emacs.d/init.el'); $stow->process_tasks();
make_path('.emacs.d'); is(
$stow->plan_stow('dotfiles');
$stow->process_tasks();
is(
readlink('.emacs.d/.emacs.d'), readlink('.emacs.d/.emacs.d'),
'../../stow/dotfiles/dot-emacs.d/dot-emacs.d', '../../stow/dotfiles/dot-emacs.d/dot-emacs.d',
=> 'processed dotfile folder exists (2 levels)' => 'processed dotfile dir exists (2 levels)'
); );
});
# subtest("stow dir marked with 'dot' prefix when directory exists in target", sub {
# process folder marked with 'dot' prefix plan tests => 1;
# when directory exists is target $stow = new_Stow(dir => '../stow', dotfiles => 1);
#
$stow = new_Stow(dir => '../stow', dotfiles => 1); make_path('../stow/dotfiles/dot-one/dot-two');
make_file('../stow/dotfiles/dot-one/dot-two/three');
make_path('.one/.two');
make_path('../stow/dotfiles/dot-one/dot-two'); $stow->plan_stow('dotfiles');
make_file('../stow/dotfiles/dot-one/dot-two/three'); $stow->process_tasks();
make_path('.one/.two'); is(
$stow->plan_stow('dotfiles');
$stow->process_tasks();
is(
readlink('./.one/.two/three'), readlink('./.one/.two/three'),
'../../../stow/dotfiles/dot-one/dot-two/three', '../../../stow/dotfiles/dot-one/dot-two/three',
=> 'processed dotfile 2 folder exists (2 levels)' => 'processed dotfile 2 dir exists (2 levels)'
); );
});
# subtest("dot-. should not have that part expanded.", sub {
# corner case: paths that have a part in them that's just "$DOT_PREFIX" or plan tests => 2;
# "$DOT_PREFIX." should not have that part expanded. $stow = new_Stow(dir => '../stow', dotfiles => 1);
#
$stow = new_Stow(dir => '../stow', dotfiles => 1); make_path('../stow/dotfiles');
make_file('../stow/dotfiles/dot-');
make_path('../stow/dotfiles'); make_path('../stow/dotfiles/dot-.');
make_file('../stow/dotfiles/dot-'); make_file('../stow/dotfiles/dot-./foo');
make_path('../stow/dotfiles/dot-.'); $stow->plan_stow('dotfiles');
make_file('../stow/dotfiles/dot-./foo'); $stow->process_tasks();
is(
$stow->plan_stow('dotfiles');
$stow->process_tasks();
is(
readlink('dot-'), readlink('dot-'),
'../stow/dotfiles/dot-', '../stow/dotfiles/dot-',
=> 'processed dotfile' => 'processed dotfile'
); );
is( is(
readlink('dot-.'), readlink('dot-.'),
'../stow/dotfiles/dot-.', '../stow/dotfiles/dot-.',
=> 'unprocessed dotfile' => 'unprocessed dotfile'
); );
});
# subtest("unstow .bar from dot-bar", sub {
# simple unstow scenario plan tests => 3;
# $stow = new_Stow(dir => '../stow', dotfiles => 1);
$stow = new_Stow(dir => '../stow', dotfiles => 1); make_path('../stow/dotfiles');
make_file('../stow/dotfiles/dot-bar');
make_link('.bar', '../stow/dotfiles/dot-bar');
make_path('../stow/dotfiles'); $stow->plan_unstow('dotfiles');
make_file('../stow/dotfiles/dot-bar'); $stow->process_tasks();
make_link('.bar', '../stow/dotfiles/dot-bar'); is($stow->get_conflict_count, 0);
ok(-f '../stow/dotfiles/dot-bar', 'package file untouched');
ok(! -e '.bar' => '.bar was unstowed');
});
$stow->plan_unstow('dotfiles'); subtest("unstow dot-emacs.d/init.el when .emacs.d/init.el in target", sub {
$stow->process_tasks(); plan tests => 4;
ok( $stow = new_Stow(dir => '../stow', dotfiles => 1);
$stow->get_conflict_count == 0 &&
-f '../stow/dotfiles/dot-bar' && ! -e '.bar'
=> 'unstow a simple dotfile'
);
# make_path('../stow/dotfiles/dot-emacs.d');
# unstow process folder marked with 'dot' prefix make_file('../stow/dotfiles/dot-emacs.d/init.el');
# when directory exists is target make_path('.emacs.d');
# make_link('.emacs.d/init.el', '../../stow/dotfiles/dot-emacs.d/init.el');
$stow = new_Stow(dir => '../stow', dotfiles => 1); $stow->plan_unstow('dotfiles');
$stow->process_tasks();
is($stow->get_conflict_count, 0);
ok(-f '../stow/dotfiles/dot-emacs.d/init.el');
ok(! -e '.emacs.d/init.el', '.emacs.d/init.el unstowed');
ok(-d '.emacs.d/' => '.emacs.d left behind');
});
make_path('../stow/dotfiles/dot-emacs.d'); subtest("unstow dot-emacs.d/init.el in --compat mode", sub {
make_file('../stow/dotfiles/dot-emacs.d/init.el'); plan tests => 4;
make_path('.emacs.d'); $stow = new_compat_Stow(dir => '../stow', dotfiles => 1);
make_link('.emacs.d/init.el', '../../stow/dotfiles/dot-emacs.d/init.el');
$stow->plan_unstow('dotfiles'); make_path('../stow/dotfiles/dot-emacs.d');
$stow->process_tasks(); make_file('../stow/dotfiles/dot-emacs.d/init.el');
ok( make_path('.emacs.d');
$stow->get_conflict_count == 0 && make_link('.emacs.d/init.el', '../../stow/dotfiles/dot-emacs.d/init.el');
-f '../stow/dotfiles/dot-emacs.d/init.el' &&
! -e '.emacs.d/init.el' && $stow->plan_unstow('dotfiles');
-d '.emacs.d/' $stow->process_tasks();
=> 'unstow dotfile folder when folder already exists' is($stow->get_conflict_count, 0);
); ok(-f '../stow/dotfiles/dot-emacs.d/init.el');
ok(! -e '.emacs.d/init.el', '.emacs.d/init.el unstowed');
ok(-d '.emacs.d/' => '.emacs.d left behind');
});

View file

@ -16,65 +16,133 @@
# along with this program. If not, see https://www.gnu.org/licenses/. # along with this program. If not, see https://www.gnu.org/licenses/.
# #
# Testing find_stowed_path() # Testing Stow:: find_stowed_path()
# #
use strict; use strict;
use warnings; use warnings;
use Test::More tests => 18; use Test::More tests => 10;
use testutil; use testutil;
use Stow::Util qw(set_debug_level); use Stow::Util qw(set_debug_level);
init_test_dirs(); init_test_dirs();
my $stow = new_Stow(dir => "$TEST_DIR/stow"); subtest("find link to a stowed path with relative target" => sub {
#set_debug_level(4); plan tests => 3;
my ($path, $stow_path, $package) = # This is a relative path, unlike $ABS_TEST_DIR below.
$stow->find_stowed_path("$TEST_DIR/target/a/b/c", "../../../stow/a/b/c"); my $target = "$TEST_DIR/target";
is($path, "$TEST_DIR/stow/a/b/c", "path");
is($stow_path, "$TEST_DIR/stow", "stow path");
is($package, "a", "package");
cd("$TEST_DIR/target"); my $stow = new_Stow(dir => "$TEST_DIR/stow", target => $target);
$stow->set_stow_dir("../stow"); my ($path, $stow_path, $package) =
($path, $stow_path, $package) =
$stow->find_stowed_path("a/b/c", "../../../stow/a/b/c"); $stow->find_stowed_path("a/b/c", "../../../stow/a/b/c");
is($path, "../stow/a/b/c", "path from target directory"); is($path, "../stow/a/b/c", "path");
is($stow_path, "../stow", "stow path from target directory"); is($stow_path, "../stow", "stow path");
is($package, "a", "from target directory"); is($package, "a", "package");
});
make_path("stow"); my $stow = new_Stow(dir => "$ABS_TEST_DIR/stow", target => "$ABS_TEST_DIR/target");
cd("../..");
$stow->set_stow_dir("$TEST_DIR/target/stow");
($path, $stow_path, $package) = # Required by creation of stow2 and stow2/.stow below
$stow->find_stowed_path("$TEST_DIR/target/a/b/c", "../../stow/a/b/c"); cd("$ABS_TEST_DIR/target");
is($path, "$TEST_DIR/target/stow/a/b/c", "path");
is($stow_path, "$TEST_DIR/target/stow", "stow path");
is($package, "a", "stow is subdir of target directory");
($path, $stow_path, $package) = subtest("find link to a stowed path" => sub {
$stow->find_stowed_path("$TEST_DIR/target/a/b/c", "../../empty"); plan tests => 3;
is($path, "", "empty path"); my ($path, $stow_path, $package) =
is($stow_path, "", "empty stow path"); $stow->find_stowed_path("a/b/c", "../../../stow/a/b/c");
is($package, "", "target is not stowed"); is($path, "../stow/a/b/c", "path from target directory");
is($stow_path, "../stow", "stow path from target directory");
is($package, "a", "from target directory");
});
make_path("$TEST_DIR/target/stow2"); subtest("find link to alien path not owned by Stow" => sub {
make_file("$TEST_DIR/target/stow2/.stow"); plan tests => 3;
my ($path, $stow_path, $package) =
$stow->find_stowed_path("a/b/c", "../../alien");
is($path, "", "alien is not stowed, so path is empty");
is($stow_path, "", "alien, so stow path is empty");
is($package, "", "alien is not stowed in any package");
});
($path, $stow_path, $package) = # Make a second stow directory within the target directory, so that we
$stow->find_stowed_path("$TEST_DIR/target/a/b/c","../../stow2/a/b/c"); # can check that links to package files within that stow directory are
is($path, "$TEST_DIR/target/stow2/a/b/c", "path"); # detected correctly.
is($stow_path, "$TEST_DIR/target/stow2", "stow path"); make_path("stow2");
is($package, "a", "detect alternate stow directory");
# Possible corner case with rogue symlink pointing to ancestor of # However this second stow directory is still "alien" to stow until we
# stow dir. # put a .stow file in it. So first test a symlink pointing to a path
($path, $stow_path, $package) = # within this second stow directory
$stow->find_stowed_path("$TEST_DIR/target/a/b/c","../../.."); subtest("second stow dir still alien without .stow" => sub {
is($path, "", "path"); plan tests => 3;
is($stow_path, "", "stow path"); my ($path, $stow_path, $package) =
is($package, "", "corner case - link points to ancestor of stow dir"); $stow->find_stowed_path("a/b/c", "../../stow2/a/b/c");
is($path, "", "stow2 not a stow dir yet, so path is empty");
is($stow_path, "", "stow2 not a stow dir yet so stow path is empty");
is($package, "", "not stowed in any recognised package yet");
});
# Now make stow2 a secondary stow directory and test that
make_file("stow2/.stow");
subtest(".stow makes second stow dir owned by Stow" => sub {
plan tests => 3;
my ($path, $stow_path, $package) =
$stow->find_stowed_path("a/b/c", "../../stow2/a/b/c");
is($path, "stow2/a/b/c", "path");
is($stow_path, "stow2", "stow path");
is($package, "a", "detect alternate stow directory");
});
subtest("relative symlink pointing to target dir" => sub {
plan tests => 3;
my ($path, $stow_path, $package) =
$stow->find_stowed_path("a/b/c", "../../..");
# Technically the target dir is not owned by Stow, since
# Stow won't touch the target dir itself, only its contents.
is($path, "", "path");
is($stow_path, "", "stow path");
is($package, "", "corner case - link points to target dir");
});
subtest("relative symlink pointing to parent of target dir" => sub {
plan tests => 3;
my ($path, $stow_path, $package) =
$stow->find_stowed_path("a/b/c", "../../../..");
is($path, "", "path");
is($stow_path, "", "stow path");
is($package, "", "corner case - link points to parent of target dir");
});
subtest("unowned symlink pointing to absolute path inside target" => sub {
plan tests => 3;
my ($path, $stow_path, $package) =
$stow->find_stowed_path("a/b/c", "$ABS_TEST_DIR/target/d");
is($path, "", "path");
is($stow_path, "", "stow path");
is($package, "", "symlink unowned by Stow points to absolute path outside target directory");
});
subtest("unowned symlink pointing to absolute path outside target" => sub {
plan tests => 3;
my ($path, $stow_path, $package) =
$stow->find_stowed_path("a/b/c", "/dev/null");
is($path, "", "path");
is($stow_path, "", "stow path");
is($package, "", "symlink unowned by Stow points to absolute path outside target directory");
});
# Now make stow2 the primary stow directory and test that it still
# works when the stow directory is under the target directory
$stow->set_stow_dir("$ABS_TEST_DIR/target/stow2");
subtest("stow2 becomes the primary stow directory" => sub {
plan tests => 3;
my ($path, $stow_path, $package) =
$stow->find_stowed_path("a/b/c", "../../stow2/a/b/c");
is($path, "stow2/a/b/c", "path in stow2");
is($stow_path, "stow2", "stow path for stow2");
is($package, "a", "stow2 is subdir of target directory");
});

View file

@ -22,91 +22,40 @@
use strict; use strict;
use warnings; use warnings;
use Stow::Util qw(join_paths); use Stow::Util qw(join_paths set_debug_level);
use Test::More tests => 14; #set_debug_level(4);
is( use Test::More tests => 22;
join_paths('a/b/c', 'd/e/f'),
'a/b/c/d/e/f' my @TESTS = (
=> 'simple' [['a/b/c', 'd/e/f'], 'a/b/c/d/e/f' => 'simple'],
[['a/b/c', '/d/e/f'], '/d/e/f' => 'relative then absolute'],
[['/a/b/c', 'd/e/f'], '/a/b/c/d/e/f' => 'absolute then relative'],
[['/a/b/c', '/d/e/f'], '/d/e/f' => 'two absolutes'],
[['/a/b/c/', '/d/e/f/'], '/d/e/f' => 'two absolutes with trailing /'],
[['///a/b///c//', '/d///////e/f'], '/d/e/f' => "multiple /'s, absolute"],
[['///a/b///c//', 'd///////e/f'], '/a/b/c/d/e/f' => "multiple /'s, relative"],
[['', 'a/b/c'], 'a/b/c' => 'first empty'],
[['a/b/c', ''], 'a/b/c' => 'second empty'],
[['/', 'a/b/c'], '/a/b/c' => 'first is /'],
[['a/b/c', '/'], '/' => 'second is /'],
[['../a1/b1/../c1/', 'a2/../b2/e2'], '../a1/c1/b2/e2' => 'relative with ../'],
[['../a1/b1/../c1/', '/a2/../b2/e2'], '/b2/e2' => 'absolute with ../'],
[['../a1/../../c1', 'a2/../../'], '../..' => 'lots of ../'],
[['./', '../a2'], '../a2' => 'drop any "./"'],
[['./a1', '../../a2'], '../a2' => 'drop any "./foo"'],
[['a/b/c', '.'], 'a/b/c' => '. on RHS'],
[['a/b/c', '.', 'd/e'], 'a/b/c/d/e' => '. in middle'],
[['0', 'a/b'], '0/a/b' => '0 at start'],
[['/0', 'a/b'], '/0/a/b' => '/0 at start'],
[['a/b/c', '0', 'd/e'], 'a/b/c/0/d/e' => '0 in middle'],
[['a/b', '0'], 'a/b/0' => '0 at end'],
); );
is( for my $test (@TESTS) {
join_paths('/a/b/c', '/d/e/f'), my ($inputs, $expected, $scenario) = @$test;
'/a/b/c/d/e/f' my $got = join_paths(@$inputs);
=> 'leading /' my $descr = "$scenario: in=[" . join(', ', map "'$_'", @$inputs) . "] exp=[$expected] got=[$got]";
); is($got, $expected, $descr);
}
is(
join_paths('/a/b/c/', '/d/e/f/'),
'/a/b/c/d/e/f'
=> 'trailing /'
);
is(
join_paths('///a/b///c//', '/d///////e/f'),
'/a/b/c/d/e/f'
=> 'mltiple /\'s'
);
is(
join_paths('', 'a/b/c'),
'a/b/c'
=> 'first empty'
);
is(
join_paths('a/b/c', ''),
'a/b/c'
=> 'second empty'
);
is(
join_paths('/', 'a/b/c'),
'/a/b/c'
=> 'first is /'
);
is(
join_paths('a/b/c', '/'),
'a/b/c'
=> 'second is /'
);
is(
join_paths('///a/b///c//', '/d///////e/f'),
'/a/b/c/d/e/f'
=> 'multiple /\'s'
);
is(
join_paths('../a1/b1/../c1/', '/a2/../b2/e2'),
'../a1/c1/b2/e2'
=> 'simple deref ".."'
);
is(
join_paths('../a1/b1/../c1/d1/e1', '../a2/../b2/c2/d2/../e2'),
'../a1/c1/d1/b2/c2/e2'
=> 'complex deref ".."'
);
is(
join_paths('../a1/../../c1', 'a2/../../'),
'../..'
=> 'too many ".."'
);
is(
join_paths('./a1', '../../a2'),
'../a2'
=> 'drop any "./"'
);
is(
join_paths('a/b/c', '.'),
'a/b/c'
=> '. on RHS'
);

88
t/link_dest_within_stow_dir.t Executable file
View file

@ -0,0 +1,88 @@
#!/usr/bin/perl
#
# This file is part of GNU Stow.
#
# GNU Stow is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# GNU Stow is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see https://www.gnu.org/licenses/.
#
# Testing Stow::link_dest_within_stow_dir()
#
use strict;
use warnings;
use Test::More tests => 6;
use testutil;
use Stow::Util;
init_test_dirs();
# This is a relative path, unlike $ABS_TEST_DIR below.
my $stow = new_Stow(dir => "$TEST_DIR/stow",
target => "$TEST_DIR/target");
subtest("relative stow dir, link to top-level package file" => sub {
plan tests => 2;
my ($package, $path) =
$stow->link_dest_within_stow_dir("../stow/pkg/dir/file");
is($package, "pkg", "package");
is($path, "dir/file", "path");
});
subtest("relative stow dir, link to second-level package file" => sub {
plan tests => 2;
my ($package, $path) =
$stow->link_dest_within_stow_dir("../stow/pkg/dir/subdir/file");
is($package, "pkg", "package");
is($path, "dir/subdir/file", "path");
});
# This is an absolute path, unlike $TEST_DIR above.
$stow = new_Stow(dir => "$ABS_TEST_DIR/stow",
target => "$ABS_TEST_DIR/target");
subtest("relative stow dir, link to second-level package file" => sub {
plan tests => 2;
my ($package, $path) =
$stow->link_dest_within_stow_dir("../stow/pkg/dir/file");
is($package, "pkg", "package");
is($path, "dir/file", "path");
});
subtest("absolute stow dir, link to top-level package file" => sub {
plan tests => 2;
my ($package, $path) =
$stow->link_dest_within_stow_dir("../stow/pkg/dir/subdir/file");
is($package, "pkg", "package");
is($path, "dir/subdir/file", "path");
});
# Links with destination in the target are not pointing within
# the stow dir, so they're not owned by stow.
subtest("link to path in target" => sub {
plan tests => 2;
my ($package, $path) =
$stow->link_dest_within_stow_dir("./alien");
is($path, "", "alien is in target, so path is empty");
is($package, "", "alien is in target, so package is empty");
});
subtest("link to path outside target and stow dir" => sub {
plan tests => 2;
my ($package, $path) =
$stow->link_dest_within_stow_dir("../alien");
is($path, "", "alien is outside, so path is empty");
is($package, "", "alien is outside, so package is empty");
});

675
t/stow.t
View file

@ -22,7 +22,7 @@
use strict; use strict;
use warnings; use warnings;
use Test::More tests => 118; use Test::More tests => 22;
use Test::Output; use Test::Output;
use English qw(-no_match_vars); use English qw(-no_match_vars);
@ -37,148 +37,171 @@ my %conflicts;
# Note that each of the following tests use a distinct set of files # Note that each of the following tests use a distinct set of files
# subtest('stow a simple tree minimally', sub {
# stow a simple tree minimally plan tests => 2;
# my $stow = new_Stow(dir => '../stow');
$stow = new_Stow(dir => '../stow');
make_path('../stow/pkg1/bin1'); make_path('../stow/pkg1/bin1');
make_file('../stow/pkg1/bin1/file1'); make_file('../stow/pkg1/bin1/file1');
$stow->plan_stow('pkg1'); $stow->plan_stow('pkg1');
$stow->process_tasks(); $stow->process_tasks();
is_deeply([ $stow->get_conflicts ], [], 'no conflicts with minimal stow'); is_deeply([ $stow->get_conflicts ], [], 'no conflicts with minimal stow');
is( is(
readlink('bin1'), readlink('bin1'),
'../stow/pkg1/bin1', '../stow/pkg1/bin1',
=> 'minimal stow of a simple tree' => 'minimal stow of a simple tree'
); );
});
# subtest('stow a simple tree into an existing directory', sub {
# stow a simple tree into an existing directory plan tests => 1;
# my $stow = new_Stow();
$stow = new_Stow();
make_path('../stow/pkg2/lib2'); make_path('../stow/pkg2/lib2');
make_file('../stow/pkg2/lib2/file2'); make_file('../stow/pkg2/lib2/file2');
make_path('lib2'); make_path('lib2');
$stow->plan_stow('pkg2'); $stow->plan_stow('pkg2');
$stow->process_tasks(); $stow->process_tasks();
is( is(
readlink('lib2/file2'), readlink('lib2/file2'),
'../../stow/pkg2/lib2/file2', '../../stow/pkg2/lib2/file2',
=> 'stow simple tree to existing directory' => 'stow simple tree to existing directory'
); );
});
# subtest('unfold existing tree', sub {
# unfold existing tree plan tests => 3;
# my $stow = new_Stow();
$stow = new_Stow();
make_path('../stow/pkg3a/bin3'); make_path('../stow/pkg3a/bin3');
make_file('../stow/pkg3a/bin3/file3a'); make_file('../stow/pkg3a/bin3/file3a');
make_link('bin3' => '../stow/pkg3a/bin3'); # emulate stow make_link('bin3' => '../stow/pkg3a/bin3'); # emulate stow
make_path('../stow/pkg3b/bin3'); make_path('../stow/pkg3b/bin3');
make_file('../stow/pkg3b/bin3/file3b'); make_file('../stow/pkg3b/bin3/file3b');
$stow->plan_stow('pkg3b'); $stow->plan_stow('pkg3b');
$stow->process_tasks(); $stow->process_tasks();
ok( ok(-d 'bin3');
-d 'bin3' && is(readlink('bin3/file3a'), '../../stow/pkg3a/bin3/file3a');
readlink('bin3/file3a') eq '../../stow/pkg3a/bin3/file3a' && is(readlink('bin3/file3b'), '../../stow/pkg3b/bin3/file3b'
readlink('bin3/file3b') eq '../../stow/pkg3b/bin3/file3b' => 'target already has 1 stowed package');
=> 'target already has 1 stowed package' });
);
# subtest("Package dir 'bin4' conflicts with existing non-dir so can't unfold", sub {
# Link to a new dir 'bin4' conflicts with existing non-dir so can't plan tests => 2;
# unfold my $stow = new_Stow();
#
$stow = new_Stow();
make_file('bin4'); # this is a file but named like a directory make_file('bin4'); # this is a file but named like a directory
make_path('../stow/pkg4/bin4'); make_path('../stow/pkg4/bin4');
make_file('../stow/pkg4/bin4/file4'); make_file('../stow/pkg4/bin4/file4');
$stow->plan_stow('pkg4'); $stow->plan_stow('pkg4');
%conflicts = $stow->get_conflicts(); %conflicts = $stow->get_conflicts();
ok( is($stow->get_conflict_count, 1);
$stow->get_conflict_count == 1 && like(
$conflicts{stow}{pkg4}[0] =~ $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' => 'link to new dir bin4 conflicts with existing non-directory'
); );
});
# subtest("Package dir 'bin4a' conflicts with existing non-dir " .
# Link to a new dir 'bin4a' conflicts with existing non-dir so can't "so can't unfold even with --adopt", sub {
# unfold even with --adopt plan tests => 2;
# my $stow = new_Stow(adopt => 1);
#$stow = new_Stow(adopt => 1);
$stow = new_Stow();
make_file('bin4a'); # this is a file but named like a directory make_file('bin4a'); # this is a file but named like a directory
make_path('../stow/pkg4a/bin4a'); make_path('../stow/pkg4a/bin4a');
make_file('../stow/pkg4a/bin4a/file4a'); make_file('../stow/pkg4a/bin4a/file4a');
$stow->plan_stow('pkg4a'); $stow->plan_stow('pkg4a');
%conflicts = $stow->get_conflicts(); %conflicts = $stow->get_conflicts();
ok( is($stow->get_conflict_count, 1);
$stow->get_conflict_count == 1 && like(
$conflicts{stow}{pkg4a}[0] =~ $conflicts{stow}{pkg4a}[0],
qr/existing target is neither a link nor a directory/ qr!cannot stow directory ../stow/pkg4a/bin4a over existing non-directory target bin4a!
=> 'link to new dir bin4a conflicts with existing non-directory' => 'link to new dir bin4a conflicts with existing non-directory'
); );
});
# subtest("Package files 'file4b' and 'bin4b' conflict with existing files", sub {
# Link to files 'file4b' and 'bin4b' conflict with existing files plan tests => 3;
# without --adopt my $stow = new_Stow();
#
$stow = new_Stow();
# Populate target # Populate target
make_file('file4b', 'file4b - version originally in target'); make_file('file4b', 'file4b - version originally in target');
make_path ('bin4b'); make_path('bin4b');
make_file('bin4b/file4b', 'bin4b/file4b - version originally in target'); make_file('bin4b/file4b', 'bin4b/file4b - version originally in target');
# Populate # Populate stow package
make_path ('../stow/pkg4b/bin4b'); make_path('../stow/pkg4b');
make_file('../stow/pkg4b/file4b', 'file4b - version originally in stow package'); make_file('../stow/pkg4b/file4b', 'file4b - version originally in stow package');
make_file('../stow/pkg4b/bin4b/file4b', 'bin4b/file4b - version originally in stow package'); make_path('../stow/pkg4b/bin4b');
make_file('../stow/pkg4b/bin4b/file4b', 'bin4b/file4b - version originally in stow package');
$stow->plan_stow('pkg4b'); $stow->plan_stow('pkg4b');
%conflicts = $stow->get_conflicts(); %conflicts = $stow->get_conflicts();
is($stow->get_conflict_count, 2 => 'conflict per file'); is($stow->get_conflict_count, 2 => 'conflict per file');
for my $i (0, 1) { for my $i (0, 1) {
my $target = $i ? 'file4b' : 'bin4b/file4b';
like( like(
$conflicts{stow}{pkg4b}[$i], $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' => 'link to file4b conflicts with existing non-directory'
); );
} }
});
# subtest("Package files 'file4d' conflicts with existing directories", sub {
# Link to files 'file4b' and 'bin4b' do not conflict with existing plan tests => 3;
# files when --adopt is given my $stow = new_Stow();
#
$stow = new_Stow(adopt => 1);
# Populate target # Populate target
make_file('file4c', "file4c - version originally in target\n"); make_path('file4d'); # this is a directory but named like a file to create the conflict
make_path ('bin4c'); make_path('bin4d/file4d'); # same here
make_file('bin4c/file4c', "bin4c/file4c - version originally in target\n");
# Populate # Populate stow package
make_path ('../stow/pkg4c/bin4c'); make_path('../stow/pkg4d');
make_file('../stow/pkg4c/file4c', "file4c - version originally in stow package\n"); make_file('../stow/pkg4d/file4d', 'file4d - version originally in stow package');
make_file('../stow/pkg4c/bin4c/file4c', "bin4c/file4c - version originally in stow package\n"); make_path('../stow/pkg4d/bin4d');
make_file('../stow/pkg4d/bin4d/file4d', 'bin4d/file4d - version originally in stow package');
$stow->plan_stow('pkg4c'); $stow->plan_stow('pkg4d');
is($stow->get_conflict_count, 0 => 'no conflicts with --adopt'); %conflicts = $stow->get_conflicts();
is($stow->get_tasks, 4 => 'two tasks per file'); is($stow->get_conflict_count, 2 => 'conflict per file');
$stow->process_tasks(); for my $i (0, 1) {
for my $file ('file4c', 'bin4c/file4c') { 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);
# Populate target
make_file('file4c', "file4c - version originally in target\n");
make_path ('bin4c');
make_file('bin4c/file4c', "bin4c/file4c - version originally in target\n");
# Populate stow package
make_path('../stow/pkg4c');
make_file('../stow/pkg4c/file4c', "file4c - version originally in stow package\n");
make_path ('../stow/pkg4c/bin4c');
make_file('../stow/pkg4c/bin4c/file4c', "bin4c/file4c - version originally in stow package\n");
$stow->plan_stow('pkg4c');
is($stow->get_conflict_count, 0 => 'no conflicts with --adopt');
is($stow->get_tasks, 4 => 'two tasks per file');
$stow->process_tasks();
for my $file ('file4c', 'bin4c/file4c') {
ok(-l $file, "$file turned into a symlink"); ok(-l $file, "$file turned into a symlink");
is( is(
readlink $file, readlink $file,
@ -186,282 +209,273 @@ for my $file ('file4c', 'bin4c/file4c') {
. "../stow/pkg4c/$file" => "$file points to right place" . "../stow/pkg4c/$file" => "$file points to right place"
); );
is(cat_file($file), "$file - version originally in target\n" => "$file has right contents"); is(cat_file($file), "$file - version originally in target\n" => "$file has right contents");
} }
});
# subtest("Target already exists but is not owned by stow", sub {
# Target already exists but is not owned by stow plan tests => 1;
# my $stow = new_Stow();
$stow = new_Stow();
make_path('bin5'); make_path('bin5');
make_invalid_link('bin5/file5','../../empty'); make_invalid_link('bin5/file5','../../empty');
make_path('../stow/pkg5/bin5/file5'); make_path('../stow/pkg5/bin5/file5');
$stow->plan_stow('pkg5'); $stow->plan_stow('pkg5');
%conflicts = $stow->get_conflicts(); %conflicts = $stow->get_conflicts();
like( like(
$conflicts{stow}{pkg5}[-1], $conflicts{stow}{pkg5}[-1],
qr/not owned by stow/ qr/not owned by stow/
=> 'target already exists but is not owned by stow' => 'target already exists but is not owned by stow'
); );
});
# subtest("Replace existing but invalid target", sub {
# Replace existing but invalid target plan tests => 1;
# my $stow = new_Stow();
$stow = new_Stow();
make_invalid_link('file6','../stow/path-does-not-exist'); make_invalid_link('file6','../stow/path-does-not-exist');
make_path('../stow/pkg6'); make_path('../stow/pkg6');
make_file('../stow/pkg6/file6'); make_file('../stow/pkg6/file6');
$stow->plan_stow('pkg6'); $stow->plan_stow('pkg6');
$stow->process_tasks(); $stow->process_tasks();
is( is(
readlink('file6'), readlink('file6'),
'../stow/pkg6/file6' '../stow/pkg6/file6'
=> 'replace existing but invalid target' => 'replace existing but invalid target'
); );
});
# subtest("Target already exists, is owned by stow, but points to a non-directory", sub {
# Target already exists, is owned by stow, but points to a non-directory plan tests => 1;
# (can't unfold) my $stow = new_Stow();
# #set_debug_level(4);
$stow = new_Stow();
#set_debug_level(4);
make_path('bin7'); make_path('bin7');
make_path('../stow/pkg7a/bin7'); make_path('../stow/pkg7a/bin7');
make_file('../stow/pkg7a/bin7/node7'); make_file('../stow/pkg7a/bin7/node7');
make_link('bin7/node7','../../stow/pkg7a/bin7/node7'); make_link('bin7/node7','../../stow/pkg7a/bin7/node7');
make_path('../stow/pkg7b/bin7/node7'); make_path('../stow/pkg7b/bin7/node7');
make_file('../stow/pkg7b/bin7/node7/file7'); make_file('../stow/pkg7b/bin7/node7/file7');
$stow->plan_stow('pkg7b'); $stow->plan_stow('pkg7b');
%conflicts = $stow->get_conflicts(); %conflicts = $stow->get_conflicts();
like( like(
$conflicts{stow}{pkg7b}[-1], $conflicts{stow}{pkg7b}[-1],
qr/existing target is stowed to a different package/ qr/existing target is stowed to a different package/
=> 'link to new dir conflicts with existing stowed non-directory' => 'link to new dir conflicts with existing stowed non-directory'
); );
});
# subtest("stowing directories named 0", sub {
# stowing directories named 0 plan tests => 4;
# my $stow = new_Stow();
$stow = new_Stow();
make_path('../stow/pkg8a/0'); make_path('../stow/pkg8a/0');
make_file('../stow/pkg8a/0/file8a'); make_file('../stow/pkg8a/0/file8a');
make_link('0' => '../stow/pkg8a/0'); # emulate stow make_link('0' => '../stow/pkg8a/0'); # emulate stow
make_path('../stow/pkg8b/0'); make_path('../stow/pkg8b/0');
make_file('../stow/pkg8b/0/file8b'); make_file('../stow/pkg8b/0/file8b');
$stow->plan_stow('pkg8b'); $stow->plan_stow('pkg8b');
$stow->process_tasks(); $stow->process_tasks();
ok( is($stow->get_conflict_count, 0);
$stow->get_conflict_count == 0 && ok(-d '0');
-d '0' && is(readlink('0/file8a'), '../../stow/pkg8a/0/file8a');
readlink('0/file8a') eq '../../stow/pkg8a/0/file8a' && is(readlink('0/file8b'), '../../stow/pkg8b/0/file8b'
readlink('0/file8b') eq '../../stow/pkg8b/0/file8b'
=> 'stowing directories named 0' => 'stowing directories named 0'
); );
});
# subtest("overriding already stowed documentation", sub {
# overriding already stowed documentation plan tests => 2;
# my $stow = new_Stow(override => ['man9', 'info9']);
$stow = new_Stow(override => ['man9', 'info9']);
make_path('../stow/pkg9a/man9/man1'); make_path('../stow/pkg9a/man9/man1');
make_file('../stow/pkg9a/man9/man1/file9.1'); make_file('../stow/pkg9a/man9/man1/file9.1');
make_path('man9/man1'); make_path('man9/man1');
make_link('man9/man1/file9.1' => '../../../stow/pkg9a/man9/man1/file9.1'); # emulate stow make_link('man9/man1/file9.1' => '../../../stow/pkg9a/man9/man1/file9.1'); # emulate stow
make_path('../stow/pkg9b/man9/man1'); make_path('../stow/pkg9b/man9/man1');
make_file('../stow/pkg9b/man9/man1/file9.1'); make_file('../stow/pkg9b/man9/man1/file9.1');
$stow->plan_stow('pkg9b'); $stow->plan_stow('pkg9b');
$stow->process_tasks(); $stow->process_tasks();
ok( is($stow->get_conflict_count, 0);
$stow->get_conflict_count == 0 && is(readlink('man9/man1/file9.1'), '../../../stow/pkg9b/man9/man1/file9.1'
readlink('man9/man1/file9.1') eq '../../../stow/pkg9b/man9/man1/file9.1'
=> 'overriding existing documentation files' => 'overriding existing documentation files'
); );
});
# subtest("deferring to already stowed documentation", sub {
# deferring to already stowed documentation plan tests => 3;
# my $stow = new_Stow(defer => ['man10', 'info10']);
$stow = new_Stow(defer => ['man10', 'info10']);
make_path('../stow/pkg10a/man10/man1'); make_path('../stow/pkg10a/man10/man1');
make_file('../stow/pkg10a/man10/man1/file10.1'); make_file('../stow/pkg10a/man10/man1/file10.1');
make_path('man10/man1'); make_path('man10/man1');
make_link('man10/man1/file10.1' => '../../../stow/pkg10a/man10/man1/file10.1'); # emulate stow make_link('man10/man1/file10.1' => '../../../stow/pkg10a/man10/man1/file10.1'); # emulate stow
make_path('../stow/pkg10b/man10/man1'); make_path('../stow/pkg10b/man10/man1');
make_file('../stow/pkg10b/man10/man1/file10.1'); make_file('../stow/pkg10b/man10/man1/file10.1');
$stow->plan_stow('pkg10b'); $stow->plan_stow('pkg10b');
is($stow->get_tasks, 0, 'no tasks to process'); is($stow->get_tasks, 0, 'no tasks to process');
ok( is($stow->get_conflict_count, 0);
$stow->get_conflict_count == 0 && is(readlink('man10/man1/file10.1'), '../../../stow/pkg10a/man10/man1/file10.1'
readlink('man10/man1/file10.1') eq '../../../stow/pkg10a/man10/man1/file10.1'
=> 'defer to existing documentation files' => 'defer to existing documentation files'
); );
});
# subtest("Ignore temp files", sub {
# Ignore temp files plan tests => 4;
# my $stow = new_Stow(ignore => ['~', '\.#.*']);
$stow = new_Stow(ignore => ['~', '\.#.*']);
make_path('../stow/pkg11/man11/man1'); make_path('../stow/pkg11/man11/man1');
make_file('../stow/pkg11/man11/man1/file11.1'); make_file('../stow/pkg11/man11/man1/file11.1');
make_file('../stow/pkg11/man11/man1/file11.1~'); make_file('../stow/pkg11/man11/man1/file11.1~');
make_file('../stow/pkg11/man11/man1/.#file11.1'); make_file('../stow/pkg11/man11/man1/.#file11.1');
make_path('man11/man1'); make_path('man11/man1');
$stow->plan_stow('pkg11'); $stow->plan_stow('pkg11');
$stow->process_tasks(); $stow->process_tasks();
ok( is($stow->get_conflict_count, 0);
$stow->get_conflict_count == 0 && is(readlink('man11/man1/file11.1'), '../../../stow/pkg11/man11/man1/file11.1');
readlink('man11/man1/file11.1') eq '../../../stow/pkg11/man11/man1/file11.1' && ok(!-e 'man11/man1/file11.1~');
!-e 'man11/man1/file11.1~' && ok(!-e 'man11/man1/.#file11.1'
!-e 'man11/man1/.#file11.1'
=> 'ignore temp files' => 'ignore temp files'
); );
});
# subtest("stowing links library files", sub {
# stowing links library files plan tests => 3;
# my $stow = new_Stow();
$stow = new_Stow();
make_path('../stow/pkg12/lib12/'); make_path('../stow/pkg12/lib12/');
make_file('../stow/pkg12/lib12/lib.so.1'); make_file('../stow/pkg12/lib12/lib.so.1');
make_link('../stow/pkg12/lib12/lib.so', 'lib.so.1'); make_link('../stow/pkg12/lib12/lib.so', 'lib.so.1');
make_path('lib12/'); make_path('lib12/');
$stow->plan_stow('pkg12'); $stow->plan_stow('pkg12');
$stow->process_tasks(); $stow->process_tasks();
ok( is($stow->get_conflict_count, 0);
$stow->get_conflict_count == 0 && is(readlink('lib12/lib.so.1'), '../../stow/pkg12/lib12/lib.so.1');
readlink('lib12/lib.so.1') eq '../../stow/pkg12/lib12/lib.so.1' && is(readlink('lib12/lib.so'), '../../stow/pkg12/lib12/lib.so'
readlink('lib12/lib.so' ) eq '../../stow/pkg12/lib12/lib.so'
=> 'stow links to libraries' => 'stow links to libraries'
); );
});
# subtest("unfolding to stow links to library files", sub {
# unfolding to stow links to library files plan tests => 5;
# my $stow = new_Stow();
$stow = new_Stow();
make_path('../stow/pkg13a/lib13/'); make_path('../stow/pkg13a/lib13/');
make_file('../stow/pkg13a/lib13/liba.so.1'); make_file('../stow/pkg13a/lib13/liba.so.1');
make_link('../stow/pkg13a/lib13/liba.so', 'liba.so.1'); make_link('../stow/pkg13a/lib13/liba.so', 'liba.so.1');
make_link('lib13','../stow/pkg13a/lib13'); make_link('lib13','../stow/pkg13a/lib13');
make_path('../stow/pkg13b/lib13/'); make_path('../stow/pkg13b/lib13/');
make_file('../stow/pkg13b/lib13/libb.so.1'); make_file('../stow/pkg13b/lib13/libb.so.1');
make_link('../stow/pkg13b/lib13/libb.so', 'libb.so.1'); make_link('../stow/pkg13b/lib13/libb.so', 'libb.so.1');
$stow->plan_stow('pkg13b'); $stow->plan_stow('pkg13b');
$stow->process_tasks(); $stow->process_tasks();
ok( is($stow->get_conflict_count, 0);
$stow->get_conflict_count == 0 && is(readlink('lib13/liba.so.1'), '../../stow/pkg13a/lib13/liba.so.1');
readlink('lib13/liba.so.1') eq '../../stow/pkg13a/lib13/liba.so.1' && is(readlink('lib13/liba.so' ), '../../stow/pkg13a/lib13/liba.so');
readlink('lib13/liba.so' ) eq '../../stow/pkg13a/lib13/liba.so' && is(readlink('lib13/libb.so.1'), '../../stow/pkg13b/lib13/libb.so.1');
readlink('lib13/libb.so.1') eq '../../stow/pkg13b/lib13/libb.so.1' && is(readlink('lib13/libb.so' ), '../../stow/pkg13b/lib13/libb.so'
readlink('lib13/libb.so' ) eq '../../stow/pkg13b/lib13/libb.so'
=> 'unfolding to stow links to libraries' => 'unfolding to stow links to libraries'
); );
});
# subtest("stowing to stow dir should fail", sub {
# stowing to stow dir should fail plan tests => 4;
# make_path('stow');
make_path('stow'); $stow = new_Stow(dir => 'stow');
$stow = new_Stow(dir => 'stow');
make_path('stow/pkg14/stow/pkg15'); make_path('stow/pkg14/stow/pkg15');
make_file('stow/pkg14/stow/pkg15/node15'); make_file('stow/pkg14/stow/pkg15/node15');
capture_stderr(); stderr_like(
$stow->plan_stow('pkg14'); sub { $stow->plan_stow('pkg14'); },
is($stow->get_tasks, 0, 'no tasks to process'); qr/WARNING: skipping target which was current stow directory stow/,
ok( "stowing to stow dir should give warning"
$stow->get_conflict_count == 0 && );
is($stow->get_tasks, 0, 'no tasks to process');
is($stow->get_conflict_count, 0);
ok(
! -l 'stow/pkg15' ! -l 'stow/pkg15'
=> "stowing to stow dir should fail" => "stowing to stow dir should fail"
); );
like($stderr, });
qr/WARNING: skipping target which was current stow directory stow/
=> "stowing to stow dir should give warning");
uncapture_stderr();
# subtest("stow a simple tree minimally when cwd isn't target", sub {
# stow a simple tree minimally when cwd isn't target plan tests => 2;
# cd('../..');
cd('../..'); $stow = new_Stow(dir => "$TEST_DIR/stow", target => "$TEST_DIR/target");
$stow = new_Stow(dir => "$TEST_DIR/stow", target => "$TEST_DIR/target");
make_path("$TEST_DIR/stow/pkg16/bin16"); make_path("$TEST_DIR/stow/pkg16/bin16");
make_file("$TEST_DIR/stow/pkg16/bin16/file16"); make_file("$TEST_DIR/stow/pkg16/bin16/file16");
$stow->plan_stow('pkg16'); $stow->plan_stow('pkg16');
$stow->process_tasks(); $stow->process_tasks();
is_deeply([ $stow->get_conflicts ], [], 'no conflicts with minimal stow'); is_deeply([ $stow->get_conflicts ], [], 'no conflicts with minimal stow');
is( is(
readlink("$TEST_DIR/target/bin16"), readlink("$TEST_DIR/target/bin16"),
'../stow/pkg16/bin16', '../stow/pkg16/bin16',
=> "minimal stow of a simple tree when cwd isn't target" => "minimal stow of a simple tree when cwd isn't target"
); );
});
# subtest("stow a simple tree minimally to absolute stow dir when cwd isn't", sub {
# stow a simple tree minimally to absolute stow dir when cwd isn't plan tests => 2;
# target my $stow = new_Stow(dir => canon_path("$TEST_DIR/stow"),
#
$stow = new_Stow(dir => canon_path("$TEST_DIR/stow"),
target => "$TEST_DIR/target"); target => "$TEST_DIR/target");
make_path("$TEST_DIR/stow/pkg17/bin17"); make_path("$TEST_DIR/stow/pkg17/bin17");
make_file("$TEST_DIR/stow/pkg17/bin17/file17"); make_file("$TEST_DIR/stow/pkg17/bin17/file17");
$stow->plan_stow('pkg17'); $stow->plan_stow('pkg17');
$stow->process_tasks(); $stow->process_tasks();
is_deeply([ $stow->get_conflicts ], [], 'no conflicts with minimal stow'); is_deeply([ $stow->get_conflicts ], [], 'no conflicts with minimal stow');
is( is(
readlink("$TEST_DIR/target/bin17"), readlink("$TEST_DIR/target/bin17"),
'../stow/pkg17/bin17', '../stow/pkg17/bin17',
=> "minimal stow of a simple tree with absolute stow dir" => "minimal stow of a simple tree with absolute stow dir"
); );
});
# subtest("stow a simple tree minimally with absolute stow AND target dirs when", sub {
# stow a simple tree minimally with absolute stow AND target dirs when plan tests => 2;
# cwd isn't target my $stow = new_Stow(dir => canon_path("$TEST_DIR/stow"),
#
$stow = new_Stow(dir => canon_path("$TEST_DIR/stow"),
target => canon_path("$TEST_DIR/target")); target => canon_path("$TEST_DIR/target"));
make_path("$TEST_DIR/stow/pkg18/bin18"); make_path("$TEST_DIR/stow/pkg18/bin18");
make_file("$TEST_DIR/stow/pkg18/bin18/file18"); make_file("$TEST_DIR/stow/pkg18/bin18/file18");
$stow->plan_stow('pkg18'); $stow->plan_stow('pkg18');
$stow->process_tasks(); $stow->process_tasks();
is_deeply([ $stow->get_conflicts ], [], 'no conflicts with minimal stow'); is_deeply([ $stow->get_conflicts ], [], 'no conflicts with minimal stow');
is( is(
readlink("$TEST_DIR/target/bin18"), readlink("$TEST_DIR/target/bin18"),
'../stow/pkg18/bin18', '../stow/pkg18/bin18',
=> "minimal stow of a simple tree with absolute stow and target dirs" => "minimal stow of a simple tree with absolute stow and target dirs"
); );
});
# subtest("stow a tree with no-folding enabled", sub {
# stow a tree with no-folding enabled - plan tests => 82;
# no new folded directories should be created, and existing # folded directories should be split open (unfolded) where
# folded directories should be split open (unfolded) where # (and only where) necessary
# (and only where) necessary #
# cd("$TEST_DIR/target");
cd("$TEST_DIR/target");
sub create_pkg { sub create_pkg {
my ($id, $pkg) = @_; my ($id, $pkg) = @_;
my $stow_pkg = "../stow/$id-$pkg"; my $stow_pkg = "../stow/$id-$pkg";
@ -493,21 +507,21 @@ sub create_pkg {
make_path ("$stow_pkg/$id-shared2/subdir-$pkg"); make_path ("$stow_pkg/$id-shared2/subdir-$pkg");
make_file("$stow_pkg/$id-shared2/$id-file-$pkg"); make_file("$stow_pkg/$id-shared2/$id-file-$pkg");
make_file("$stow_pkg/$id-shared2/subdir-$pkg/$id-file-$pkg"); make_file("$stow_pkg/$id-shared2/subdir-$pkg/$id-file-$pkg");
} }
foreach my $pkg (qw{a b}) { foreach my $pkg (qw{a b}) {
create_pkg('no-folding', $pkg); create_pkg('no-folding', $pkg);
} }
$stow = new_Stow('no-folding' => 1); $stow = new_Stow('no-folding' => 1);
$stow->plan_stow('no-folding-a'); $stow->plan_stow('no-folding-a');
is_deeply([ $stow->get_conflicts ], [] => 'no conflicts with --no-folding'); is_deeply([ $stow->get_conflicts ], [] => 'no conflicts with --no-folding');
my @tasks = $stow->get_tasks; my @tasks = $stow->get_tasks;
use Data::Dumper; use Data::Dumper;
is(scalar(@tasks), 13 => "6 dirs, 7 links") || warn Dumper(\@tasks); is(scalar(@tasks), 13 => "6 dirs, 7 links") || warn Dumper(\@tasks);
$stow->process_tasks(); $stow->process_tasks();
sub check_no_folding { sub check_no_folding {
my ($pkg) = @_; my ($pkg) = @_;
my $stow_pkg = "../stow/no-folding-$pkg"; my $stow_pkg = "../stow/no-folding-$pkg";
is_link("no-folding-file-$pkg", "$stow_pkg/no-folding-file-$pkg"); is_link("no-folding-file-$pkg", "$stow_pkg/no-folding-file-$pkg");
@ -541,16 +555,17 @@ sub check_no_folding {
"../$stow_pkg/no-folding-shared2/no-folding-file-$pkg"); "../$stow_pkg/no-folding-shared2/no-folding-file-$pkg");
is_link("no-folding-shared2/no-folding-file-$pkg", is_link("no-folding-shared2/no-folding-file-$pkg",
"../$stow_pkg/no-folding-shared2/no-folding-file-$pkg"); "../$stow_pkg/no-folding-shared2/no-folding-file-$pkg");
} }
check_no_folding('a'); check_no_folding('a');
$stow = new_Stow('no-folding' => 1); $stow = new_Stow('no-folding' => 1);
$stow->plan_stow('no-folding-b'); $stow->plan_stow('no-folding-b');
is_deeply([ $stow->get_conflicts ], [] => 'no conflicts with --no-folding'); is_deeply([ $stow->get_conflicts ], [] => 'no conflicts with --no-folding');
@tasks = $stow->get_tasks; @tasks = $stow->get_tasks;
is(scalar(@tasks), 11 => '4 dirs, 7 links') || warn Dumper(\@tasks); is(scalar(@tasks), 11 => '4 dirs, 7 links') || warn Dumper(\@tasks);
$stow->process_tasks(); $stow->process_tasks();
check_no_folding('a'); check_no_folding('a');
check_no_folding('b'); check_no_folding('b');
});

View file

@ -24,11 +24,10 @@ package testutil;
use strict; use strict;
use warnings; use warnings;
use Carp qw(croak); use Carp qw(confess croak);
use File::Basename; use File::Basename;
use File::Path qw(make_path remove_tree); use File::Path qw(make_path remove_tree);
use File::Spec; use File::Spec;
use IO::Scalar;
use Test::More; use Test::More;
use Stow; use Stow;
@ -38,7 +37,6 @@ use base qw(Exporter);
our @EXPORT = qw( our @EXPORT = qw(
$ABS_TEST_DIR $ABS_TEST_DIR
$TEST_DIR $TEST_DIR
$stderr
init_test_dirs init_test_dirs
cd cd
new_Stow new_compat_Stow new_Stow new_compat_Stow
@ -46,45 +44,41 @@ our @EXPORT = qw(
remove_dir remove_file remove_link remove_dir remove_file remove_link
cat_file cat_file
is_link is_dir_not_symlink is_nonexistent_path is_link is_dir_not_symlink is_nonexistent_path
capture_stderr uncapture_stderr
); );
our $TEST_DIR = 'tmp-testing-trees'; our $TEST_DIR = 'tmp-testing-trees';
our $ABS_TEST_DIR = File::Spec->rel2abs('tmp-testing-trees'); our $ABS_TEST_DIR = File::Spec->rel2abs('tmp-testing-trees');
our $stderr;
my $tied_err;
sub capture_stderr {
undef $stderr;
$tied_err = tie *STDERR, 'IO::Scalar', \$stderr;
}
sub uncapture_stderr {
undef $tied_err;
untie *STDERR;
}
sub init_test_dirs { sub init_test_dirs {
my $test_dir = shift || $TEST_DIR;
my $abs_test_dir = File::Spec->rel2abs($test_dir);
# Create a run_from/ subdirectory for tests which want to run # Create a run_from/ subdirectory for tests which want to run
# from a separate directory outside the Stow directory or # from a separate directory outside the Stow directory or
# target directory. # target directory.
for my $dir ("target", "stow", "run_from") { for my $dir ("target", "stow", "run_from") {
my $path = "$TEST_DIR/$dir"; my $path = "$test_dir/$dir";
-d $path and remove_tree($path); -d $path and remove_tree($path);
make_path($path); make_path($path);
} }
# Don't let user's ~/.stow-global-ignore affect test results # Don't let user's ~/.stow-global-ignore affect test results
$ENV{HOME} = $ABS_TEST_DIR; $ENV{HOME} = $abs_test_dir;
return $abs_test_dir;
} }
sub new_Stow { sub new_Stow {
my %opts = @_; my %opts = @_;
# These default paths assume that execution will be triggered from
# within the target directory.
$opts{dir} ||= '../stow'; $opts{dir} ||= '../stow';
$opts{target} ||= '.'; $opts{target} ||= '.';
$opts{test_mode} = 1; $opts{test_mode} = 1;
return new Stow(%opts); my $stow = eval { new Stow(%opts) };
if ($@) {
confess "Error while trying to instantiate new Stow(%opts): $@";
}
return $stow;
} }
sub new_compat_Stow { sub new_compat_Stow {
@ -96,28 +90,28 @@ sub new_compat_Stow {
#===== SUBROUTINE =========================================================== #===== SUBROUTINE ===========================================================
# Name : make_link() # Name : make_link()
# Purpose : safely create a link # Purpose : safely create a link
# Parameters: $target => path to the link # Parameters: $link_src => path to the link
# : $source => where the new link should point # : $link_dest => where the new link should point
# : $invalid => true iff $source refers to non-existent file # : $invalid => true iff $link_dest refers to non-existent file
# Returns : n/a # Returns : n/a
# Throws : fatal error if the link can not be safely created # Throws : fatal error if the link can not be safely created
# Comments : checks for existing nodes # Comments : checks for existing nodes
#============================================================================ #============================================================================
sub make_link { sub make_link {
my ($target, $source, $invalid) = @_; my ($link_src, $link_dest, $invalid) = @_;
if (-l $target) { if (-l $link_src) {
my $old_source = readlink join('/', parent($target), $source) my $old_source = readlink join('/', parent($link_src), $link_dest)
or die "$target is already a link but could not read link $target/$source"; or croak "$link_src is already a link but could not read link $link_src/$link_dest";
if ($old_source ne $source) { if ($old_source ne $link_dest) {
die "$target already exists but points elsewhere\n"; croak "$link_src already exists but points elsewhere\n";
} }
} }
die "$target already exists and is not a link\n" if -e $target; croak "$link_src already exists and is not a link\n" if -e $link_src;
my $abs_target = File::Spec->rel2abs($target); my $abs_target = File::Spec->rel2abs($link_src);
my $target_container = dirname($abs_target); my $link_src_container = dirname($abs_target);
my $abs_source = File::Spec->rel2abs($source, $target_container); my $abs_source = File::Spec->rel2abs($link_dest, $link_src_container);
#warn "t $target c $target_container as $abs_source"; #warn "t $link_src c $link_src_container as $abs_source";
if (-e $abs_source) { if (-e $abs_source) {
croak "Won't make invalid link pointing to existing $abs_target" croak "Won't make invalid link pointing to existing $abs_target"
if $invalid; if $invalid;
@ -126,8 +120,8 @@ sub make_link {
croak "Won't make link pointing to non-existent $abs_target" croak "Won't make link pointing to non-existent $abs_target"
unless $invalid; unless $invalid;
} }
symlink $source, $target symlink $link_dest, $link_src
or die "could not create link $target => $source ($!)\n"; or croak "could not create link $link_src => $link_dest ($!)\n";
} }
#===== SUBROUTINE =========================================================== #===== SUBROUTINE ===========================================================
@ -157,11 +151,11 @@ sub make_file {
my ($path, $contents) = @_; my ($path, $contents) = @_;
if (-e $path and ! -f $path) { if (-e $path and ! -f $path) {
die "a non-file already exists at $path\n"; croak "a non-file already exists at $path\n";
} }
open my $FILE ,'>', $path open my $FILE ,'>', $path
or die "could not create file: $path ($!)\n"; or croak "could not create file: $path ($!)\n";
print $FILE $contents if defined $contents; print $FILE $contents if defined $contents;
close $FILE; close $FILE;
} }
@ -178,9 +172,9 @@ sub make_file {
sub remove_link { sub remove_link {
my ($path) = @_; my ($path) = @_;
if (not -l $path) { if (not -l $path) {
die qq(remove_link() called with a non-link: $path); croak qq(remove_link() called with a non-link: $path);
} }
unlink $path or die "could not remove link: $path ($!)\n"; unlink $path or croak "could not remove link: $path ($!)\n";
return; return;
} }
@ -195,9 +189,9 @@ sub remove_link {
sub remove_file { sub remove_file {
my ($path) = @_; my ($path) = @_;
if (-z $path) { if (-z $path) {
die "file at $path is non-empty\n"; croak "file at $path is non-empty\n";
} }
unlink $path or die "could not remove empty file: $path ($!)\n"; unlink $path or croak "could not remove empty file: $path ($!)\n";
return; return;
} }
@ -213,10 +207,10 @@ sub remove_dir {
my ($dir) = @_; my ($dir) = @_;
if (not -d $dir) { if (not -d $dir) {
die "$dir is not a directory"; croak "$dir is not a directory";
} }
opendir my $DIR, $dir or die "cannot read directory: $dir ($!)\n"; opendir my $DIR, $dir or croak "cannot read directory: $dir ($!)\n";
my @listing = readdir $DIR; my @listing = readdir $DIR;
closedir $DIR; closedir $DIR;
@ -227,16 +221,16 @@ sub remove_dir {
my $path = "$dir/$node"; my $path = "$dir/$node";
if (-l $path or (-f $path and -z $path) or $node eq $Stow::LOCAL_IGNORE_FILE) { if (-l $path or (-f $path and -z $path) or $node eq $Stow::LOCAL_IGNORE_FILE) {
unlink $path or die "cannot unlink $path ($!)\n"; unlink $path or croak "cannot unlink $path ($!)\n";
} }
elsif (-d "$path") { elsif (-d "$path") {
remove_dir($path); remove_dir($path);
} }
else { else {
die "$path is not a link, directory, or empty file\n"; croak "$path is not a link, directory, or empty file\n";
} }
} }
rmdir $dir or die "cannot rmdir $dir ($!)\n"; rmdir $dir or croak "cannot rmdir $dir ($!)\n";
return; return;
} }
@ -251,7 +245,7 @@ sub remove_dir {
#============================================================================ #============================================================================
sub cd { sub cd {
my ($dir) = @_; my ($dir) = @_;
chdir $dir or die "Failed to chdir($dir): $!\n"; chdir $dir or croak "Failed to chdir($dir): $!\n";
} }
#===== SUBROUTINE =========================================================== #===== SUBROUTINE ===========================================================
@ -264,7 +258,7 @@ sub cd {
#============================================================================ #============================================================================
sub cat_file { sub cat_file {
my ($file) = @_; my ($file) = @_;
open F, $file or die "Failed to open($file): $!\n"; open F, $file or croak "Failed to open($file): $!\n";
my $contents = join '', <F>; my $contents = join '', <F>;
close(F); close(F);
return $contents; return $contents;
@ -309,6 +303,5 @@ sub is_nonexistent_path {
# Local variables: # Local variables:
# mode: perl # mode: perl
# cperl-indent-level: 4
# end: # end:
# vim: ft=perl # vim: ft=perl

View file

@ -22,429 +22,528 @@
use strict; use strict;
use warnings; use warnings;
use Test::More tests => 39; use File::Spec qw(make_path);
use POSIX qw(getcwd);
use Test::More tests => 35;
use Test::Output; use Test::Output;
use English qw(-no_match_vars); use English qw(-no_match_vars);
use testutil; use testutil;
use Stow::Util qw(canon_path); use Stow::Util qw(canon_path);
init_test_dirs(); my $repo = getcwd();
cd("$TEST_DIR/target");
# Note that each of the following tests use a distinct set of files init_test_dirs($TEST_DIR);
my $stow; our $COMPAT_TEST_DIR = "${TEST_DIR}-compat";
my %conflicts; our $COMPAT_ABS_TEST_DIR = init_test_dirs($COMPAT_TEST_DIR);
sub init_stow2 {
make_path('stow2'); # make our alternate stow dir a subdir of target
make_file('stow2/.stow');
}
sub create_unowned_files {
# Make things harder for Stow to figure out, by adding
# a bunch of alien files unrelated to Stow.
my @UNOWNED_DIRS = ('unowned-dir', '.unowned-dir', 'dot-unowned-dir');
for my $dir ('.', @UNOWNED_DIRS) {
for my $subdir ('.', @UNOWNED_DIRS) {
make_path("$dir/$subdir");
make_file("$dir/$subdir/unowned");
make_file("$dir/$subdir/.unowned");
make_file("$dir/$subdir/dot-unowned");
}
}
}
# Run a subtest twice, with compat off then on, in parallel test trees.
# #
# unstow a simple tree minimally # Params: $name[, $setup], $test_code
# #
$stow = new_Stow(); # $setup is an optional ref to an options hash to pass into the new
# Stow() constructor, or a ref to a sub which performs setup before
# the constructor gets called and then returns that options hash.
sub subtests {
my $name = shift;
my $setup = @_ == 2 ? shift : {};
my $code = shift;
make_path('../stow/pkg1/bin1'); $ENV{HOME} = $ABS_TEST_DIR;
make_file('../stow/pkg1/bin1/file1'); cd($repo);
make_link('bin1', '../stow/pkg1/bin1'); cd("$TEST_DIR/target");
create_unowned_files();
# cd first to allow setup to cd somewhere else.
my $opts = ref($setup) eq 'HASH' ? $setup : $setup->($TEST_DIR);
subtest($name, sub {
make_path($opts->{dir}) if $opts->{dir};
my $stow = new_Stow(%$opts);
$code->($stow, $TEST_DIR);
});
$stow->plan_unstow('pkg1'); $ENV{HOME} = $COMPAT_ABS_TEST_DIR;
$stow->process_tasks(); cd($repo);
ok( cd("$COMPAT_TEST_DIR/target");
$stow->get_conflict_count == 0 && create_unowned_files();
-f '../stow/pkg1/bin1/file1' && ! -e 'bin1' # cd first to allow setup to cd somewhere else.
=> 'unstow a simple tree' $opts = ref $setup eq 'HASH' ? $setup : $setup->($COMPAT_TEST_DIR);
); subtest("$name (compat mode)", sub {
make_path($opts->{dir}) if $opts->{dir};
my $stow = new_compat_Stow(%$opts);
$code->($stow, $COMPAT_TEST_DIR);
});
}
# sub plan_tests {
# unstow a simple tree from an existing directory my ($stow, $count) = @_;
# plan tests => $stow->{compat} ? $count + 2 : $count;
$stow = new_Stow(); }
make_path('lib2'); subtests("unstow a simple tree minimally", sub {
make_path('../stow/pkg2/lib2'); my ($stow) = @_;
make_file('../stow/pkg2/lib2/file2'); plan tests => 3;
make_link('lib2/file2', '../../stow/pkg2/lib2/file2');
$stow->plan_unstow('pkg2'); make_path('../stow/pkg1/bin1');
$stow->process_tasks(); make_file('../stow/pkg1/bin1/file1');
ok( make_link('bin1', '../stow/pkg1/bin1');
$stow->get_conflict_count == 0 &&
-f '../stow/pkg2/lib2/file2' && -d 'lib2' $stow->plan_unstow('pkg1');
$stow->process_tasks();
is($stow->get_conflict_count, 0, 'conflict count');
ok(-f '../stow/pkg1/bin1/file1');
ok(! -e 'bin1' => 'unstow a simple tree');
});
subtests("unstow a simple tree from an existing directory", sub {
my ($stow) = @_;
plan tests => 3;
make_path('lib2');
make_path('../stow/pkg2/lib2');
make_file('../stow/pkg2/lib2/file2');
make_link('lib2/file2', '../../stow/pkg2/lib2/file2');
$stow->plan_unstow('pkg2');
$stow->process_tasks();
is($stow->get_conflict_count, 0, 'conflict count');
ok(-f '../stow/pkg2/lib2/file2');
ok(-d 'lib2'
=> 'unstow simple tree from a pre-existing directory' => 'unstow simple tree from a pre-existing directory'
); );
});
# subtests("fold tree after unstowing", sub {
# fold tree after unstowing my ($stow) = @_;
# plan tests => 3;
$stow = new_Stow();
make_path('bin3'); make_path('bin3');
make_path('../stow/pkg3a/bin3'); make_path('../stow/pkg3a/bin3');
make_file('../stow/pkg3a/bin3/file3a'); make_file('../stow/pkg3a/bin3/file3a');
make_link('bin3/file3a' => '../../stow/pkg3a/bin3/file3a'); # emulate stow make_link('bin3/file3a' => '../../stow/pkg3a/bin3/file3a'); # emulate stow
make_path('../stow/pkg3b/bin3'); make_path('../stow/pkg3b/bin3');
make_file('../stow/pkg3b/bin3/file3b'); make_file('../stow/pkg3b/bin3/file3b');
make_link('bin3/file3b' => '../../stow/pkg3b/bin3/file3b'); # emulate stow make_link('bin3/file3b' => '../../stow/pkg3b/bin3/file3b'); # emulate stow
$stow->plan_unstow('pkg3b'); $stow->plan_unstow('pkg3b');
$stow->process_tasks(); $stow->process_tasks();
ok( is($stow->get_conflict_count, 0, 'conflict count');
$stow->get_conflict_count == 0 && ok(-l 'bin3');
-l 'bin3' && is(readlink('bin3'), '../stow/pkg3a/bin3'
readlink('bin3') eq '../stow/pkg3a/bin3'
=> 'fold tree after unstowing' => 'fold tree after unstowing'
); );
});
# subtests("existing link is owned by stow but is invalid so it gets removed anyway", sub {
# existing link is owned by stow but is invalid so it gets removed anyway my ($stow) = @_;
# plan tests => 2;
$stow = new_Stow();
make_path('bin4'); make_path('bin4');
make_path('../stow/pkg4/bin4'); make_path('../stow/pkg4/bin4');
make_file('../stow/pkg4/bin4/file4'); make_file('../stow/pkg4/bin4/file4');
make_invalid_link('bin4/file4', '../../stow/pkg4/bin4/does-not-exist'); make_invalid_link('bin4/file4', '../../stow/pkg4/bin4/does-not-exist');
$stow->plan_unstow('pkg4'); $stow->plan_unstow('pkg4');
$stow->process_tasks(); $stow->process_tasks();
ok( is($stow->get_conflict_count, 0, 'conflict count');
$stow->get_conflict_count == 0 && ok(! -e 'bin4/file4'
! -e 'bin4/file4'
=> q(remove invalid link owned by stow) => q(remove invalid link owned by stow)
); );
});
# subtests("Existing invalid link is not owned by stow", sub {
# Existing link is not owned by stow my ($stow) = @_;
# plan tests => 3;
$stow = new_Stow();
make_path('../stow/pkg5/bin5'); make_path('../stow/pkg5/bin5');
make_invalid_link('bin5', '../not-stow'); make_invalid_link('bin5', '../not-stow');
$stow->plan_unstow('pkg5'); $stow->plan_unstow('pkg5');
%conflicts = $stow->get_conflicts; is($stow->get_conflict_count, 0, 'conflict count');
like( ok(-l 'bin5', 'invalid link not removed');
$conflicts{unstow}{pkg5}[-1], is(readlink('bin5'), '../not-stow' => "invalid link not changed");
qr(existing target is not owned by stow) });
=> q(existing link not owned by stow)
);
# subtests("Target already exists, is owned by stow, but points to a different package", sub {
# Target already exists, is owned by stow, but points to a different package my ($stow) = @_;
# plan tests => 3;
$stow = new_Stow();
make_path('bin6'); make_path('bin6');
make_path('../stow/pkg6a/bin6'); make_path('../stow/pkg6a/bin6');
make_file('../stow/pkg6a/bin6/file6'); make_file('../stow/pkg6a/bin6/file6');
make_link('bin6/file6', '../../stow/pkg6a/bin6/file6'); make_link('bin6/file6', '../../stow/pkg6a/bin6/file6');
make_path('../stow/pkg6b/bin6'); make_path('../stow/pkg6b/bin6');
make_file('../stow/pkg6b/bin6/file6'); make_file('../stow/pkg6b/bin6/file6');
$stow->plan_unstow('pkg6b'); $stow->plan_unstow('pkg6b');
ok( is($stow->get_conflict_count, 0, 'conflict count');
$stow->get_conflict_count == 0 && ok(-l 'bin6/file6');
-l 'bin6/file6' && is(
readlink('bin6/file6') eq '../../stow/pkg6a/bin6/file6' readlink('bin6/file6'),
'../../stow/pkg6a/bin6/file6'
=> q(ignore existing link that points to a different package) => q(ignore existing link that points to a different package)
); );
});
# subtests("Don't unlink anything under the stow directory",
# Don't unlink anything under the stow directory sub {
# make_path('stow');
make_path('stow'); # make out stow dir a subdir of target return { dir => 'stow' };
$stow = new_Stow(dir => 'stow'); # target dir defaults to parent of stow, which is target directory
},
sub {
plan tests => 5;
my ($stow) = @_;
# emulate stowing into ourself (bizarre corner case or accident) # Emulate stowing into ourself (bizarre corner case or accident):
make_path('stow/pkg7a/stow/pkg7b'); make_path('stow/pkg7a/stow/pkg7b');
make_file('stow/pkg7a/stow/pkg7b/file7b'); make_file('stow/pkg7a/stow/pkg7b/file7b');
make_link('stow/pkg7b', '../stow/pkg7a/stow/pkg7b'); # Make a package be a link to a package of the same name inside another package.
make_link('stow/pkg7b', '../stow/pkg7a/stow/pkg7b');
$stow->plan_unstow('pkg7b'); stderr_like(
is($stow->get_tasks, 0, 'no tasks to process when unstowing pkg7b'); sub { $stow->plan_unstow('pkg7b'); },
ok( $stow->{compat} ? qr/WARNING: skipping target which was current stow directory stow/ : qr//
$stow->get_conflict_count == 0 && => "warn when unstowing from ourself"
-l 'stow/pkg7b' && );
readlink('stow/pkg7b') eq '../stow/pkg7a/stow/pkg7b' is($stow->get_tasks, 0, 'no tasks to process when unstowing pkg7b');
is($stow->get_conflict_count, 0, 'conflict count');
ok(-l 'stow/pkg7b');
is(
readlink('stow/pkg7b'),
'../stow/pkg7a/stow/pkg7b'
=> q(don't unlink any nodes under the stow directory) => q(don't unlink any nodes under the stow directory)
); );
});
subtests("Don't unlink any nodes under another stow directory",
sub {
make_path('stow');
return { dir => 'stow' };
},
sub {
my ($stow) = @_;
plan tests => 5;
# init_stow2();
# Don't unlink any nodes under another stow directory # emulate stowing into ourself (bizarre corner case or accident)
# make_path('stow/pkg8a/stow2/pkg8b');
$stow = new_Stow(dir => 'stow'); make_file('stow/pkg8a/stow2/pkg8b/file8b');
make_link('stow2/pkg8b', '../stow/pkg8a/stow2/pkg8b');
make_path('stow2'); # make our alternate stow dir a subdir of target stderr_like(
make_file('stow2/.stow'); sub { $stow->plan_unstow('pkg8a'); },
qr/WARNING: skipping marked Stow directory stow2/
# emulate stowing into ourself (bizarre corner case or accident) => "warn when skipping unstowing"
make_path('stow/pkg8a/stow2/pkg8b'); );
make_file('stow/pkg8a/stow2/pkg8b/file8b'); is($stow->get_tasks, 0, 'no tasks to process when unstowing pkg8a');
make_link('stow2/pkg8b', '../stow/pkg8a/stow2/pkg8b'); is($stow->get_conflict_count, 0, 'conflict count');
ok(-l 'stow2/pkg8b');
capture_stderr(); is(
$stow->plan_unstow('pkg8a'); readlink('stow2/pkg8b'),
is($stow->get_tasks, 0, 'no tasks to process when unstowing pkg8a'); '../stow/pkg8a/stow2/pkg8b'
ok(
$stow->get_conflict_count == 0 &&
-l 'stow2/pkg8b' &&
readlink('stow2/pkg8b') eq '../stow/pkg8a/stow2/pkg8b'
=> q(don't unlink any nodes under another stow directory) => q(don't unlink any nodes under another stow directory)
); );
like($stderr, });
qr/WARNING: skipping protected directory stow2/
=> "unstowing from ourself should skip stow");
uncapture_stderr();
# # This will be used by subsequent tests
# overriding already stowed documentation sub check_protected_dirs_skipped {
# my ($stderr) = @_;
$stow = new_Stow(override => ['man9', 'info9']); for my $dir (qw{stow stow2}) {
make_file('stow/.stow'); like($stderr,
qr/WARNING: skipping marked Stow directory $dir/
=> "warn when skipping marked directory $dir");
}
}
make_path('../stow/pkg9a/man9/man1'); subtests("overriding already stowed documentation",
make_file('../stow/pkg9a/man9/man1/file9.1'); {override => ['man9', 'info9']},
make_path('man9/man1'); sub {
make_link('man9/man1/file9.1' => '../../../stow/pkg9a/man9/man1/file9.1'); # emulate stow my ($stow) = @_;
plan_tests($stow, 2);
make_path('../stow/pkg9b/man9/man1'); make_file('stow/.stow');
make_file('../stow/pkg9b/man9/man1/file9.1'); init_stow2();
$stow->plan_unstow('pkg9b'); make_path('../stow/pkg9a/man9/man1');
$stow->process_tasks(); make_file('../stow/pkg9a/man9/man1/file9.1');
ok( make_path('man9/man1');
$stow->get_conflict_count == 0 && make_link('man9/man1/file9.1' => '../../../stow/pkg9a/man9/man1/file9.1'); # emulate stow
!-l 'man9/man1/file9.1'
make_path('../stow/pkg9b/man9/man1');
make_file('../stow/pkg9b/man9/man1/file9.1');
my $stderr = stderr_from { $stow->plan_unstow('pkg9b') };
check_protected_dirs_skipped($stderr) if $stow->{compat};
$stow->process_tasks();
is($stow->get_conflict_count, 0, 'conflict count');
ok(!-l 'man9/man1/file9.1'
=> 'overriding existing documentation files' => 'overriding existing documentation files'
); );
});
# subtests("deferring to already stowed documentation",
# deferring to already stowed documentation {defer => ['man10', 'info10']},
# sub {
$stow = new_Stow(defer => ['man10', 'info10']); my ($stow) = @_;
plan_tests($stow, 3);
make_path('../stow/pkg10a/man10/man1'); init_stow2();
make_file('../stow/pkg10a/man10/man1/file10a.1'); make_path('../stow/pkg10a/man10/man1');
make_path('man10/man1'); make_file('../stow/pkg10a/man10/man1/file10a.1');
make_link('man10/man1/file10a.1' => '../../../stow/pkg10a/man10/man1/file10a.1'); make_path('man10/man1');
make_link('man10/man1/file10a.1' => '../../../stow/pkg10a/man10/man1/file10a.1');
# need this to block folding # need this to block folding
make_path('../stow/pkg10b/man10/man1'); make_path('../stow/pkg10b/man10/man1');
make_file('../stow/pkg10b/man10/man1/file10b.1'); make_file('../stow/pkg10b/man10/man1/file10b.1');
make_link('man10/man1/file10b.1' => '../../../stow/pkg10b/man10/man1/file10b.1'); make_link('man10/man1/file10b.1' => '../../../stow/pkg10b/man10/man1/file10b.1');
make_path('../stow/pkg10c/man10/man1');
make_path('../stow/pkg10c/man10/man1'); make_file('../stow/pkg10c/man10/man1/file10a.1');
make_file('../stow/pkg10c/man10/man1/file10a.1'); my $stderr = stderr_from { $stow->plan_unstow('pkg10c') };
$stow->plan_unstow('pkg10c'); check_protected_dirs_skipped($stderr) if $stow->{compat};
is($stow->get_tasks, 0, 'no tasks to process when unstowing pkg10c'); is($stow->get_tasks, 0, 'no tasks to process when unstowing pkg10c');
ok( is($stow->get_conflict_count, 0, 'conflict count');
$stow->get_conflict_count == 0 && is(
readlink('man10/man1/file10a.1') eq '../../../stow/pkg10a/man10/man1/file10a.1' readlink('man10/man1/file10a.1'),
'../../../stow/pkg10a/man10/man1/file10a.1'
=> 'defer to existing documentation files' => 'defer to existing documentation files'
); );
});
# subtests("Ignore temp files",
# Ignore temp files {ignore => ['~', '\.#.*']},
# sub {
$stow = new_Stow(ignore => ['~', '\.#.*']); my ($stow) = @_;
plan_tests($stow, 2);
make_path('../stow/pkg12/man12/man1'); init_stow2();
make_file('../stow/pkg12/man12/man1/file12.1'); make_path('../stow/pkg12/man12/man1');
make_file('../stow/pkg12/man12/man1/file12.1~'); make_file('../stow/pkg12/man12/man1/file12.1');
make_file('../stow/pkg12/man12/man1/.#file12.1'); make_file('../stow/pkg12/man12/man1/file12.1~');
make_path('man12/man1'); make_file('../stow/pkg12/man12/man1/.#file12.1');
make_link('man12/man1/file12.1' => '../../../stow/pkg12/man12/man1/file12.1'); make_path('man12/man1');
make_link('man12/man1/file12.1' => '../../../stow/pkg12/man12/man1/file12.1');
$stow->plan_unstow('pkg12'); my $stderr = stderr_from { $stow->plan_unstow('pkg12') };
$stow->process_tasks(); check_protected_dirs_skipped($stderr) if $stow->{compat};
ok( $stow->process_tasks();
$stow->get_conflict_count == 0 && is($stow->get_conflict_count, 0, 'conflict count');
!-e 'man12/man1/file12.1' ok(! -e 'man12/man1/file12.1' => 'man12/man1/file12.1 was unstowed');
=> 'ignore temp files' });
);
# subtests("Unstow an already unstowed package", sub {
# Unstow an already unstowed package my ($stow) = @_;
# plan_tests($stow, 2);
$stow = new_Stow();
$stow->plan_unstow('pkg12');
is($stow->get_tasks, 0, 'no tasks to process when unstowing pkg12');
ok(
$stow->get_conflict_count == 0
=> 'unstow already unstowed package pkg12'
);
# my $stderr = stderr_from { $stow->plan_unstow('pkg12') };
# Unstow a never stowed package check_protected_dirs_skipped($stderr) if $stow->{compat};
# is($stow->get_tasks, 0, 'no tasks to process when unstowing pkg12');
is($stow->get_conflict_count, 0, 'conflict count');
});
eval { remove_dir("$TEST_DIR/target"); }; subtests("Unstow a never stowed package", sub {
mkdir("$TEST_DIR/target"); my ($stow) = @_;
plan tests => 2;
$stow = new_Stow(); eval { remove_dir($stow->{target}); };
$stow->plan_unstow('pkg12'); mkdir($stow->{target});
is($stow->get_tasks, 0, 'no tasks to process when unstowing pkg12 which was never stowed');
ok(
$stow->get_conflict_count == 0
=> 'unstow never stowed package pkg12'
);
# $stow->plan_unstow('pkg12');
# Unstowing when target contains a real file shouldn't be an issue. is($stow->get_tasks, 0, 'no tasks to process when unstowing pkg12 which was never stowed');
# is($stow->get_conflict_count, 0, 'conflict count');
make_file('man12/man1/file12.1'); });
$stow = new_Stow(); subtests("Unstowing when target contains real files shouldn't be an issue", sub {
$stow->plan_unstow('pkg12'); my ($stow) = @_;
is($stow->get_tasks, 0, 'no tasks to process when unstowing pkg12 for third time'); plan tests => 4;
%conflicts = $stow->get_conflicts;
ok(
$stow->get_conflict_count == 1 &&
$conflicts{unstow}{pkg12}[0]
=~ m!existing target is neither a link nor a directory: man12/man1/file12\.1!
=> 'unstow pkg12 for third time'
);
# # Test both a file which do / don't overlap with the package
# unstow a simple tree minimally when cwd isn't target make_path('man12/man1');
# make_file('man12/man1/alien');
cd('../..'); make_file('man12/man1/file12.1');
$stow = new_Stow(dir => "$TEST_DIR/stow", target => "$TEST_DIR/target");
make_path("$TEST_DIR/stow/pkg13/bin13"); $stow->plan_unstow('pkg12');
make_file("$TEST_DIR/stow/pkg13/bin13/file13"); is($stow->get_tasks, 0, 'no tasks to process when unstowing pkg12 for third time');
make_link("$TEST_DIR/target/bin13", '../stow/pkg13/bin13'); is($stow->get_conflict_count, 0, 'conflict count');
ok(-f 'man12/man1/alien', 'alien untouched');
ok(-f 'man12/man1/file12.1', 'file overlapping with pkg untouched');
});
$stow->plan_unstow('pkg13'); subtests("unstow a simple tree minimally when cwd isn't target",
$stow->process_tasks(); sub {
ok( my $test_dir = shift;
$stow->get_conflict_count == 0 && cd($repo);
-f "$TEST_DIR/stow/pkg13/bin13/file13" && ! -e "$TEST_DIR/target/bin13" return {
=> 'unstow a simple tree' dir => "$test_dir/stow",
); target => "$test_dir/target"
}
},
sub {
my ($stow, $test_dir) = @_;
plan tests => 3;
# make_path("$test_dir/stow/pkg13/bin13");
# unstow a simple tree minimally with absolute stow dir when cwd isn't make_file("$test_dir/stow/pkg13/bin13/file13");
# target make_link("$test_dir/target/bin13", '../stow/pkg13/bin13');
#
$stow = new_Stow(dir => canon_path("$TEST_DIR/stow"),
target => "$TEST_DIR/target");
make_path("$TEST_DIR/stow/pkg14/bin14"); $stow->plan_unstow('pkg13');
make_file("$TEST_DIR/stow/pkg14/bin14/file14"); $stow->process_tasks();
make_link("$TEST_DIR/target/bin14", '../stow/pkg14/bin14'); is($stow->get_conflict_count, 0, 'conflict count');
ok(-f "$test_dir/stow/pkg13/bin13/file13", 'package file untouched');
ok(! -e "$test_dir/target/bin13" => 'bin13/ unstowed');
});
$stow->plan_unstow('pkg14'); subtests("unstow a simple tree minimally with absolute stow dir when cwd isn't target",
$stow->process_tasks(); sub {
ok( my $test_dir = shift;
$stow->get_conflict_count == 0 && cd($repo);
-f "$TEST_DIR/stow/pkg14/bin14/file14" && ! -e "$TEST_DIR/target/bin14" return {
dir => canon_path("$test_dir/stow"),
target => "$test_dir/target"
};
},
sub {
plan tests => 3;
my ($stow, $test_dir) = @_;
make_path("$test_dir/stow/pkg14/bin14");
make_file("$test_dir/stow/pkg14/bin14/file14");
make_link("$test_dir/target/bin14", '../stow/pkg14/bin14');
$stow->plan_unstow('pkg14');
$stow->process_tasks();
is($stow->get_conflict_count, 0, 'conflict count');
ok(-f "$test_dir/stow/pkg14/bin14/file14");
ok(! -e "$test_dir/target/bin14"
=> 'unstow a simple tree with absolute stow dir' => 'unstow a simple tree with absolute stow dir'
); );
});
# subtests("unstow a simple tree minimally with absolute stow AND target dirs when cwd isn't target",
# unstow a simple tree minimally with absolute stow AND target dirs sub {
# when cwd isn't target my $test_dir = shift;
# cd($repo);
$stow = new_Stow(dir => canon_path("$TEST_DIR/stow"), return {
target => canon_path("$TEST_DIR/target")); dir => canon_path("$test_dir/stow"),
target => canon_path("$test_dir/target")
};
},
sub {
my ($stow, $test_dir) = @_;
plan tests => 3;
make_path("$TEST_DIR/stow/pkg15/bin15"); make_path("$test_dir/stow/pkg15/bin15");
make_file("$TEST_DIR/stow/pkg15/bin15/file15"); make_file("$test_dir/stow/pkg15/bin15/file15");
make_link("$TEST_DIR/target/bin15", '../stow/pkg15/bin15'); make_link("$test_dir/target/bin15", '../stow/pkg15/bin15');
$stow->plan_unstow('pkg15'); $stow->plan_unstow('pkg15');
$stow->process_tasks(); $stow->process_tasks();
ok( is($stow->get_conflict_count, 0, 'conflict count');
$stow->get_conflict_count == 0 && ok(-f "$test_dir/stow/pkg15/bin15/file15");
-f "$TEST_DIR/stow/pkg15/bin15/file15" && ! -e "$TEST_DIR/target/bin15" ok(! -e "$test_dir/target/bin15"
=> 'unstow a simple tree with absolute stow and target dirs' => 'unstow a simple tree with absolute stow and target dirs'
); );
});
#
# unstow a tree with no-folding enabled -
# no refolding should take place
#
cd("$TEST_DIR/target");
sub create_and_stow_pkg { sub create_and_stow_pkg {
my ($id, $pkg) = @_; my ($id, $pkg) = @_;
my $stow_pkg = "../stow/$id-$pkg"; my $stow_pkg = "../stow/$id-$pkg";
make_path ($stow_pkg); make_path($stow_pkg);
make_file("$stow_pkg/$id-file-$pkg"); make_file("$stow_pkg/$id-file-$pkg");
# create a shallow hierarchy specific to this package and stow # create a shallow hierarchy specific to this package and stow
# via folding # via folding
make_path ("$stow_pkg/$id-$pkg-only-folded"); make_path("$stow_pkg/$id-$pkg-only-folded");
make_file("$stow_pkg/$id-$pkg-only-folded/file-$pkg"); make_file("$stow_pkg/$id-$pkg-only-folded/file-$pkg");
make_link("$id-$pkg-only-folded", "$stow_pkg/$id-$pkg-only-folded"); make_link("$id-$pkg-only-folded", "$stow_pkg/$id-$pkg-only-folded");
# create a deeper hierarchy specific to this package and stow # create a deeper hierarchy specific to this package and stow
# via folding # via folding
make_path ("$stow_pkg/$id-$pkg-only-folded2/subdir"); make_path("$stow_pkg/$id-$pkg-only-folded2/subdir");
make_file("$stow_pkg/$id-$pkg-only-folded2/subdir/file-$pkg"); make_file("$stow_pkg/$id-$pkg-only-folded2/subdir/file-$pkg");
make_link("$id-$pkg-only-folded2", make_link("$id-$pkg-only-folded2",
"$stow_pkg/$id-$pkg-only-folded2"); "$stow_pkg/$id-$pkg-only-folded2");
# create a shallow hierarchy specific to this package and stow # create a shallow hierarchy specific to this package and stow
# without folding # without folding
make_path ("$stow_pkg/$id-$pkg-only-unfolded"); make_path("$stow_pkg/$id-$pkg-only-unfolded");
make_file("$stow_pkg/$id-$pkg-only-unfolded/file-$pkg"); make_file("$stow_pkg/$id-$pkg-only-unfolded/file-$pkg");
make_path ("$id-$pkg-only-unfolded"); make_path("$id-$pkg-only-unfolded");
make_link("$id-$pkg-only-unfolded/file-$pkg", make_link("$id-$pkg-only-unfolded/file-$pkg",
"../$stow_pkg/$id-$pkg-only-unfolded/file-$pkg"); "../$stow_pkg/$id-$pkg-only-unfolded/file-$pkg");
# create a deeper hierarchy specific to this package and stow # create a deeper hierarchy specific to this package and stow
# without folding # without folding
make_path ("$stow_pkg/$id-$pkg-only-unfolded2/subdir"); make_path("$stow_pkg/$id-$pkg-only-unfolded2/subdir");
make_file("$stow_pkg/$id-$pkg-only-unfolded2/subdir/file-$pkg"); make_file("$stow_pkg/$id-$pkg-only-unfolded2/subdir/file-$pkg");
make_path ("$id-$pkg-only-unfolded2/subdir"); make_path("$id-$pkg-only-unfolded2/subdir");
make_link("$id-$pkg-only-unfolded2/subdir/file-$pkg", make_link("$id-$pkg-only-unfolded2/subdir/file-$pkg",
"../../$stow_pkg/$id-$pkg-only-unfolded2/subdir/file-$pkg"); "../../$stow_pkg/$id-$pkg-only-unfolded2/subdir/file-$pkg");
# create a shallow shared hierarchy which this package uses, and stow # create a shallow shared hierarchy which this package uses, and stow
# its contents without folding # its contents without folding
make_path ("$stow_pkg/$id-shared"); make_path("$stow_pkg/$id-shared");
make_file("$stow_pkg/$id-shared/file-$pkg"); make_file("$stow_pkg/$id-shared/file-$pkg");
make_path ("$id-shared"); make_path("$id-shared");
make_link("$id-shared/file-$pkg", make_link("$id-shared/file-$pkg",
"../$stow_pkg/$id-shared/file-$pkg"); "../$stow_pkg/$id-shared/file-$pkg");
# create a deeper shared hierarchy which this package uses, and stow # create a deeper shared hierarchy which this package uses, and stow
# its contents without folding # its contents without folding
make_path ("$stow_pkg/$id-shared2/subdir"); make_path("$stow_pkg/$id-shared2/subdir");
make_file("$stow_pkg/$id-shared2/file-$pkg"); make_file("$stow_pkg/$id-shared2/file-$pkg");
make_file("$stow_pkg/$id-shared2/subdir/file-$pkg"); make_file("$stow_pkg/$id-shared2/subdir/file-$pkg");
make_path ("$id-shared2/subdir"); make_path("$id-shared2/subdir");
make_link("$id-shared2/file-$pkg", make_link("$id-shared2/file-$pkg",
"../$stow_pkg/$id-shared2/file-$pkg"); "../$stow_pkg/$id-shared2/file-$pkg");
make_link("$id-shared2/subdir/file-$pkg", make_link("$id-shared2/subdir/file-$pkg",
"../../$stow_pkg/$id-shared2/subdir/file-$pkg"); "../../$stow_pkg/$id-shared2/subdir/file-$pkg");
} }
foreach my $pkg (qw{a b}) { subtest("unstow a tree with no-folding enabled - no refolding should take place", sub {
cd("$TEST_DIR/target");
plan tests => 15;
foreach my $pkg (qw{a b}) {
create_and_stow_pkg('no-folding', $pkg); create_and_stow_pkg('no-folding', $pkg);
} }
$stow = new_Stow('no-folding' => 1); my $stow = new_Stow('no-folding' => 1);
$stow->plan_unstow('no-folding-b'); $stow->plan_unstow('no-folding-b');
is_deeply([ $stow->get_conflicts ], [] => 'no conflicts with --no-folding'); is_deeply([ $stow->get_conflicts ], [] => 'no conflicts with --no-folding');
use Data::Dumper;
#warn Dumper($stow->get_tasks);
$stow->process_tasks(); $stow->process_tasks();
is_nonexistent_path('no-folding-b-only-folded'); is_nonexistent_path('no-folding-b-only-folded');
is_nonexistent_path('no-folding-b-only-folded2'); is_nonexistent_path('no-folding-b-only-folded2');
is_nonexistent_path('no-folding-b-only-unfolded/file-b'); is_nonexistent_path('no-folding-b-only-unfolded/file-b');
is_nonexistent_path('no-folding-b-only-unfolded2/subdir/file-b'); is_nonexistent_path('no-folding-b-only-unfolded2/subdir/file-b');
is_dir_not_symlink('no-folding-shared'); is_dir_not_symlink('no-folding-shared');
is_dir_not_symlink('no-folding-shared2'); is_dir_not_symlink('no-folding-shared2');
is_dir_not_symlink('no-folding-shared2/subdir'); is_dir_not_symlink('no-folding-shared2/subdir');
});
# subtests("Test cleaning up subdirs with --paranoid option", sub {
# Todo # TODO
# # });
# Test cleaning up subdirs with --paranoid option

View file

@ -1,403 +0,0 @@
#!/usr/bin/perl
#
# This file is part of GNU Stow.
#
# GNU Stow is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# GNU Stow is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see https://www.gnu.org/licenses/.
#
# Test unstowing packages in compat mode
#
use strict;
use warnings;
use File::Spec qw(make_path);
use Test::More tests => 37;
use Test::Output;
use English qw(-no_match_vars);
use testutil;
use Stow::Util qw(canon_path);
init_test_dirs();
cd("$TEST_DIR/target");
# Note that each of the following tests use a distinct set of files
my $stow;
my %conflicts;
#
# unstow a simple tree minimally
#
$stow = new_compat_Stow();
make_path('../stow/pkg1/bin1');
make_file('../stow/pkg1/bin1/file1');
make_link('bin1', '../stow/pkg1/bin1');
$stow->plan_unstow('pkg1');
$stow->process_tasks();
ok(
$stow->get_conflict_count == 0 &&
-f '../stow/pkg1/bin1/file1' && ! -e 'bin1'
=> 'unstow a simple tree'
);
#
# unstow a simple tree from an existing directory
#
$stow = new_compat_Stow();
make_path('lib2');
make_path('../stow/pkg2/lib2');
make_file('../stow/pkg2/lib2/file2');
make_link('lib2/file2', '../../stow/pkg2/lib2/file2');
$stow->plan_unstow('pkg2');
$stow->process_tasks();
ok(
$stow->get_conflict_count == 0 &&
-f '../stow/pkg2/lib2/file2' && -d 'lib2'
=> 'unstow simple tree from a pre-existing directory'
);
#
# fold tree after unstowing
#
$stow = new_compat_Stow();
make_path('bin3');
make_path('../stow/pkg3a/bin3');
make_file('../stow/pkg3a/bin3/file3a');
make_link('bin3/file3a' => '../../stow/pkg3a/bin3/file3a'); # emulate stow
make_path('../stow/pkg3b/bin3');
make_file('../stow/pkg3b/bin3/file3b');
make_link('bin3/file3b' => '../../stow/pkg3b/bin3/file3b'); # emulate stow
$stow->plan_unstow('pkg3b');
$stow->process_tasks();
ok(
$stow->get_conflict_count == 0 &&
-l 'bin3' &&
readlink('bin3') eq '../stow/pkg3a/bin3'
=> 'fold tree after unstowing'
);
#
# existing link is owned by stow but is invalid so it gets removed anyway
#
$stow = new_compat_Stow();
make_path('bin4');
make_path('../stow/pkg4/bin4');
make_file('../stow/pkg4/bin4/file4');
make_invalid_link('bin4/file4', '../../stow/pkg4/bin4/does-not-exist');
$stow->plan_unstow('pkg4');
$stow->process_tasks();
ok(
$stow->get_conflict_count == 0 &&
! -e 'bin4/file4'
=> q(remove invalid link owned by stow)
);
#
# Existing link is not owned by stow
#
$stow = new_compat_Stow();
make_path('../stow/pkg5/bin5');
make_invalid_link('bin5', '../not-stow');
$stow->plan_unstow('pkg5');
# Unlike the corresponding stow_contents.t test, this doesn't
# cause any conflicts.
#
#like(
# $Conflicts[-1], qr(can't unlink.*not owned by stow)
# => q(existing link not owned by stow)
#);
ok(
-l 'bin5' && readlink('bin5') eq '../not-stow'
=> q(existing link not owned by stow)
);
#
# Target already exists, is owned by stow, but points to a different package
#
$stow = new_compat_Stow();
make_path('bin6');
make_path('../stow/pkg6a/bin6');
make_file('../stow/pkg6a/bin6/file6');
make_link('bin6/file6', '../../stow/pkg6a/bin6/file6');
make_path('../stow/pkg6b/bin6');
make_file('../stow/pkg6b/bin6/file6');
$stow->plan_unstow('pkg6b');
ok(
$stow->get_conflict_count == 0 &&
-l 'bin6/file6' &&
readlink('bin6/file6') eq '../../stow/pkg6a/bin6/file6'
=> q(ignore existing link that points to a different package)
);
#
# Don't unlink anything under the stow directory
#
make_path('stow'); # make out stow dir a subdir of target
$stow = new_compat_Stow(dir => 'stow');
# emulate stowing into ourself (bizarre corner case or accident)
make_path('stow/pkg7a/stow/pkg7b');
make_file('stow/pkg7a/stow/pkg7b/file7b');
make_link('stow/pkg7b', '../stow/pkg7a/stow/pkg7b');
capture_stderr();
$stow->plan_unstow('pkg7b');
is($stow->get_tasks, 0, 'no tasks to process when unstowing pkg7b');
ok(
$stow->get_conflict_count == 0 &&
-l 'stow/pkg7b' &&
readlink('stow/pkg7b') eq '../stow/pkg7a/stow/pkg7b'
=> q(don't unlink any nodes under the stow directory)
);
like($stderr,
qr/WARNING: skipping target which was current stow directory stow/
=> "warn when unstowing from ourself");
uncapture_stderr();
#
# Don't unlink any nodes under another stow directory
#
$stow = new_compat_Stow(dir => 'stow');
make_path('stow2'); # make our alternate stow dir a subdir of target
make_file('stow2/.stow');
# emulate stowing into ourself (bizarre corner case or accident)
make_path('stow/pkg8a/stow2/pkg8b');
make_file('stow/pkg8a/stow2/pkg8b/file8b');
make_link('stow2/pkg8b', '../stow/pkg8a/stow2/pkg8b');
capture_stderr();
$stow->plan_unstow('pkg8a');
is($stow->get_tasks, 0, 'no tasks to process when unstowing pkg8a');
ok(
$stow->get_conflict_count == 0 &&
-l 'stow2/pkg8b' &&
readlink('stow2/pkg8b') eq '../stow/pkg8a/stow2/pkg8b'
=> q(don't unlink any nodes under another stow directory)
);
like($stderr,
qr/WARNING: skipping target which was current stow directory stow/
=> "warn when skipping unstowing");
uncapture_stderr();
#
# overriding already stowed documentation
#
# This will be used by this and subsequent tests
sub check_protected_dirs_skipped {
for my $dir (qw{stow stow2}) {
like($stderr,
qr/WARNING: skipping protected directory $dir/
=> "warn when skipping protected directory $dir");
}
uncapture_stderr();
}
$stow = new_compat_Stow(override => ['man9', 'info9']);
make_file('stow/.stow');
make_path('../stow/pkg9a/man9/man1');
make_file('../stow/pkg9a/man9/man1/file9.1');
make_path('man9/man1');
make_link('man9/man1/file9.1' => '../../../stow/pkg9a/man9/man1/file9.1'); # emulate stow
make_path('../stow/pkg9b/man9/man1');
make_file('../stow/pkg9b/man9/man1/file9.1');
capture_stderr();
$stow->plan_unstow('pkg9b');
$stow->process_tasks();
ok(
$stow->get_conflict_count == 0 &&
!-l 'man9/man1/file9.1'
=> 'overriding existing documentation files'
);
check_protected_dirs_skipped();
#
# deferring to already stowed documentation
#
$stow = new_compat_Stow(defer => ['man10', 'info10']);
make_path('../stow/pkg10a/man10/man1');
make_file('../stow/pkg10a/man10/man1/file10a.1');
make_path('man10/man1');
make_link('man10/man1/file10a.1' => '../../../stow/pkg10a/man10/man1/file10a.1');
# need this to block folding
make_path('../stow/pkg10b/man10/man1');
make_file('../stow/pkg10b/man10/man1/file10b.1');
make_link('man10/man1/file10b.1' => '../../../stow/pkg10b/man10/man1/file10b.1');
make_path('../stow/pkg10c/man10/man1');
make_file('../stow/pkg10c/man10/man1/file10a.1');
capture_stderr();
$stow->plan_unstow('pkg10c');
is($stow->get_tasks, 0, 'no tasks to process when unstowing pkg10c');
ok(
$stow->get_conflict_count == 0 &&
readlink('man10/man1/file10a.1') eq '../../../stow/pkg10a/man10/man1/file10a.1'
=> 'defer to existing documentation files'
);
check_protected_dirs_skipped();
#
# Ignore temp files
#
$stow = new_compat_Stow(ignore => ['~', '\.#.*']);
make_path('../stow/pkg12/man12/man1');
make_file('../stow/pkg12/man12/man1/file12.1');
make_file('../stow/pkg12/man12/man1/file12.1~');
make_file('../stow/pkg12/man12/man1/.#file12.1');
make_path('man12/man1');
make_link('man12/man1/file12.1' => '../../../stow/pkg12/man12/man1/file12.1');
capture_stderr();
$stow->plan_unstow('pkg12');
$stow->process_tasks();
ok(
$stow->get_conflict_count == 0 &&
!-e 'man12/man1/file12.1'
=> 'ignore temp files'
);
check_protected_dirs_skipped();
#
# Unstow an already unstowed package
#
$stow = new_compat_Stow();
capture_stderr();
$stow->plan_unstow('pkg12');
is($stow->get_tasks, 0, 'no tasks to process when unstowing pkg12');
ok(
$stow->get_conflict_count == 0
=> 'unstow already unstowed package pkg12'
);
check_protected_dirs_skipped();
#
# Unstow a never stowed package
#
eval { remove_dir("$TEST_DIR/target"); };
mkdir("$TEST_DIR/target");
$stow = new_compat_Stow();
capture_stderr();
$stow->plan_unstow('pkg12');
is($stow->get_tasks, 0, 'no tasks to process when unstowing pkg12 which was never stowed');
ok(
$stow->get_conflict_count == 0
=> 'unstow never stowed package pkg12'
);
check_protected_dirs_skipped();
#
# Unstowing when target contains a real file shouldn't be an issue.
#
make_file('man12/man1/file12.1');
$stow = new_compat_Stow();
capture_stderr();
$stow->plan_unstow('pkg12');
is($stow->get_tasks, 0, 'no tasks to process when unstowing pkg12 for third time');
%conflicts = $stow->get_conflicts;
ok(
$stow->get_conflict_count == 1 &&
$conflicts{unstow}{pkg12}[0]
=~ m!existing target is neither a link nor a directory: man12/man1/file12\.1!
=> 'unstow pkg12 for third time'
);
check_protected_dirs_skipped();
#
# unstow a simple tree minimally when cwd isn't target
#
cd('../..');
$stow = new_Stow(dir => "$TEST_DIR/stow", target => "$TEST_DIR/target");
make_path("$TEST_DIR/stow/pkg13/bin13");
make_file("$TEST_DIR/stow/pkg13/bin13/file13");
make_link("$TEST_DIR/target/bin13", '../stow/pkg13/bin13');
$stow->plan_unstow('pkg13');
$stow->process_tasks();
ok(
$stow->get_conflict_count == 0 &&
-f "$TEST_DIR/stow/pkg13/bin13/file13" && ! -e "$TEST_DIR/target/bin13"
=> 'unstow a simple tree'
);
#
# unstow a simple tree minimally with absolute stow dir when cwd isn't
# target
#
$stow = new_Stow(dir => canon_path("$TEST_DIR/stow"),
target => "$TEST_DIR/target");
make_path("$TEST_DIR/stow/pkg14/bin14");
make_file("$TEST_DIR/stow/pkg14/bin14/file14");
make_link("$TEST_DIR/target/bin14", '../stow/pkg14/bin14');
$stow->plan_unstow('pkg14');
$stow->process_tasks();
ok(
$stow->get_conflict_count == 0 &&
-f "$TEST_DIR/stow/pkg14/bin14/file14" && ! -e "$TEST_DIR/target/bin14"
=> 'unstow a simple tree with absolute stow dir'
);
#
# unstow a simple tree minimally with absolute stow AND target dirs
# when cwd isn't target
#
$stow = new_Stow(dir => canon_path("$TEST_DIR/stow"),
target => canon_path("$TEST_DIR/target"));
make_path("$TEST_DIR/stow/pkg15/bin15");
make_file("$TEST_DIR/stow/pkg15/bin15/file15");
make_link("$TEST_DIR/target/bin15", '../stow/pkg15/bin15');
$stow->plan_unstow('pkg15');
$stow->process_tasks();
ok(
$stow->get_conflict_count == 0 &&
-f "$TEST_DIR/stow/pkg15/bin15/file15" && ! -e "$TEST_DIR/target/bin15"
=> 'unstow a simple tree with absolute stow and target dirs'
);
# Todo
#
# Test cleaning up subdirs with --paranoid option