From cc63fd0dc16500fd4fabb151f57b92382dee4644 Mon Sep 17 00:00:00 2001 From: Joey Territo Date: Sat, 25 Jul 2020 21:59:30 -0400 Subject: [PATCH 01/19] First attempt at implementing negative ignore patterns --- dotdrop/utils.py | 30 ++++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/dotdrop/utils.py b/dotdrop/utils.py index 0b9f0c6..e1c3fe2 100644 --- a/dotdrop/utils.py +++ b/dotdrop/utils.py @@ -203,9 +203,18 @@ def must_ignore(paths, ignores, debug=False): return False if debug: LOG.dbg('must ignore? \"{}\" against {}'.format(paths, ignores)) + do_not_ignore = [] for p in paths: for i in ignores: - if fnmatch.fnmatch(p, i): + if i.startswith('!'): + i = i[1:] + if fnmatch.fnmatch(p, i): + if debug: + LOG.dbg('negative ignore \"{}\" match: {}'.format('!' + i, p)) + do_not_ignore.append(p) + elif fnmatch.fnmatch(p, i): + if p in do_not_ignore: + continue if debug: LOG.dbg('ignore \"{}\" match: {}'.format(i, p)) return True @@ -229,18 +238,31 @@ def patch_ignores(ignores, prefix, debug=False): if debug: LOG.dbg('ignores before patching: {}'.format(ignores)) for ignore in ignores: + negative = ignore.startswith('!') + if negative: + ignore = ignore[1:] + if os.path.isabs(ignore): # is absolute - new.append(ignore) + if negative: + new.append('!' + ignore) + else: + new.append(ignore) continue if STAR in ignore: if ignore.startswith(STAR) or ignore.startswith(os.sep): # is glob - new.append(ignore) + if negative: + new.append('!' + ignore) + else: + new.append(ignore) continue # patch ignore path = os.path.join(prefix, ignore) - new.append(path) + if negative: + new.append('!' + path) + else: + new.append(path) if debug: LOG.dbg('ignores after patching: {}'.format(new)) return new From 8feb668ccd853f1a2eeaea4fcac1491a5d3426f4 Mon Sep 17 00:00:00 2001 From: Joey Territo Date: Tue, 28 Jul 2020 17:44:10 -0400 Subject: [PATCH 02/19] Better implementation of ignore patterns --- dotdrop/utils.py | 38 ++++++++++++++++++++++++++------------ 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/dotdrop/utils.py b/dotdrop/utils.py index e1c3fe2..09340bf 100644 --- a/dotdrop/utils.py +++ b/dotdrop/utils.py @@ -13,6 +13,7 @@ import fnmatch import inspect import importlib import filecmp +import itertools from shutil import rmtree, which # local import @@ -203,21 +204,25 @@ def must_ignore(paths, ignores, debug=False): return False if debug: LOG.dbg('must ignore? \"{}\" against {}'.format(paths, ignores)) - do_not_ignore = [] + ignored_negative, ignored = categorize(lambda ign: ign.startswith('!'), ignores) for p in paths: - for i in ignores: - if i.startswith('!'): - i = i[1:] - if fnmatch.fnmatch(p, i): - if debug: - LOG.dbg('negative ignore \"{}\" match: {}'.format('!' + i, p)) - do_not_ignore.append(p) - elif fnmatch.fnmatch(p, i): - if p in do_not_ignore: - continue + ignore_matches = [] + # First ignore dotfiles + for i in ignored: + if fnmatch.fnmatch(p, i): if debug: LOG.dbg('ignore \"{}\" match: {}'.format(i, p)) - return True + ignore_matches.append(p) + # Then remove any matches that actually shouldn't be ignored + for ni in ignored_negative: + # Each of these will start with an '!' so we need to remove that + ni = ni[1:] + if fnmatch.fnmatch(p, ni): + if debug: + LOG.dbg('negative ignore \"{}\" match: {}'.format(ni, p)) + ignore_matches.remove(p) + if ignore_matches: + return True if debug: LOG.dbg('NOT ignoring {}'.format(paths)) return False @@ -374,3 +379,12 @@ def adapt_workers(options, logger): if options.dry and options.workers > 1: logger.warn('workers set to 1 when --dry is used') options.workers = 1 + + +def categorize(function, iterable): + """separate an iterable into elements for which + function(element) is true for each element and + for which function(element) is false for each + element""" + return tuple(filter(function, iterable)),\ + tuple(itertools.filterfalse(function, iterable)) From 10f9f7917e964a5508701fbdc5fd59ca474fcf8e Mon Sep 17 00:00:00 2001 From: Joey Territo Date: Sat, 5 Sep 2020 23:05:23 -0400 Subject: [PATCH 03/19] Add unit test for negative ignore patterns in install command --- tests-ng/install-negative-ignore.sh | 125 ++++++++++++++++++++++++++++ 1 file changed, 125 insertions(+) create mode 100755 tests-ng/install-negative-ignore.sh diff --git a/tests-ng/install-negative-ignore.sh b/tests-ng/install-negative-ignore.sh new file mode 100755 index 0000000..f6b7fa2 --- /dev/null +++ b/tests-ng/install-negative-ignore.sh @@ -0,0 +1,125 @@ +#!/usr/bin/env bash +# author: jtt9340 (https://github.com/jtt9340) +# +# test install negative ignore absolute/relative +# returns 1 in case of error +# + +# exit on first error +#set -e + +# all this crap to get current path +if [ $(uname) = Darwin ]; then + # Unfortunately, readlink works differently on macOS than it does on GNU/Linux + # (the -f option behaves differently) and the realpath command does not exist. + # Workarounds I find on the Internet suggest just using Homebrew to install coreutils + # so you can get the GNU coreutils on your Mac. But, I don't want this script to + # assume (a) users have Homebrew installed and (b) if they have Homebrew installed, that + # they then installed the GNU coreutils. + readlink() { + TARGET_FILE=$1 + + cd `dirname $TARGET_FILE` + TARGET_FILE=`basename $TARGET_FILE` + + # Iterate down a (possible) chain of symlinks + while [ -L "$TARGET_FILE" ]; do + TARGET_FILE=`readlink $TARGET_FILE` + cd `dirname $TARGET_FILE` + TARGET_FILE=`basename $TARGET_FILE` + done + + # Compute the canonicalized name by finding the physical path + # for the directory we're in and appending the target file. + PHYS_DIR=`pwd -P` + RESULT=$PHYS_DIR/$TARGET_FILE + echo $RESULT + } + rl="readlink" +else + rl="readlink -f" + if ! ${rl} "${0}" >/dev/null 2>&1; then + rl="realpath" + + if ! hash ${rl}; then + echo "\"${rl}\" not found !" && exit 1 + fi + fi +fi +cur=$(dirname "$(${rl} "${0}")") + +# dotdrop path can be pass as argument +ddpath="${cur}/../" +[ "${1}" != "" ] && ddpath="${1}" +[ ! -d ${ddpath} ] && echo "ddpath \"${ddpath}\" is not a directory" && exit 1 + +export PYTHONPATH="${ddpath}:${PYTHONPATH}" +bin="python3 -m dotdrop.dotdrop" + +echo "dotdrop path: ${ddpath}" +echo "pythonpath: ${PYTHONPATH}" + +# get the helpers +source ${cur}/helpers + +echo -e "$(tput setaf 6)==> RUNNING $(basename $BASH_SOURCE) <==$(tput sgr0)" + +################################################################ +# this is the test +################################################################ + +# dotdrop directory +basedir=`mktemp -d --suffix='-dotdrop-tests' 2>/dev/null || mktemp -d` +echo "[+] dotdrop dir: ${basedir}" +echo "[+] dotpath dir: ${basedir}/dotfiles" + +# the dotfile to be imported +tmpd=`mktemp -d --suffix='-dotdrop-tests' 2>/dev/null || mktemp -d` + +# some files +mkdir -p ${tmpd}/program/ignore_me +echo "some data" > ${tmpd}/program/a +echo "some data" > ${tmpd}/program/ignore_me/b +echo "some data" > ${tmpd}/program/ignore_me/c + +# create the config file +cfg="${basedir}/config.yaml" +create_conf ${cfg} # sets token + +# import +echo "[+] import" +cd ${ddpath} | ${bin} import -c ${cfg} ${tmpd}/program + +# adding ignore in dotfile +cfg2="${basedir}/config2.yaml" +sed '/d_program:/a\ +\ \ \ \ instignore:\ +\ \ \ \ - "*/ignore_me/*"\ +\ \ \ \ - "!*/ignore_me/c" +' ${cfg} > ${cfg2} + +# install +rm -rf ${tmpd} +echo "[+] install with negative ignore in dotfile" +cd ${ddpath} | ${bin} install -c ${cfg2} --verbose +[ "$?" != "0" ] && exit 1 +echo '(1) expect structure to be +. +└── program + ├── a + └── ignore_me + └── c' + +[[ -n "$(find ${tmpd}/program -name a)" ]] || exit 1 +echo "(1) found program/a ... good" +[[ -n "$(find ${tmpd}/program/ignore_me -name b)" ]] && exit 1 +echo "(1) didn't find program/b ... good" +[[ -n "$(find ${tmpd}/program/ignore_me -name c)" ]] || exit 1 +echo "(1) found program/c ... good" + +## CLEANING +rm -rf ${basedir} ${tmpd} + +echo "OK" +exit 0 + From 0b58578aa0e5c2e9219e0aae6039cbbee1b742a8 Mon Sep 17 00:00:00 2001 From: Joey Territo Date: Mon, 14 Dec 2020 20:32:25 -0500 Subject: [PATCH 04/19] Add unit test for negative ignore patterns in compare command --- dotdrop/utils.py | 1 + tests-ng/compare-negative-ignore.sh | 132 ++++++++++++++++++++++++++++ 2 files changed, 133 insertions(+) create mode 100755 tests-ng/compare-negative-ignore.sh diff --git a/dotdrop/utils.py b/dotdrop/utils.py index 09340bf..a601992 100644 --- a/dotdrop/utils.py +++ b/dotdrop/utils.py @@ -220,6 +220,7 @@ def must_ignore(paths, ignores, debug=False): if fnmatch.fnmatch(p, ni): if debug: LOG.dbg('negative ignore \"{}\" match: {}'.format(ni, p)) + # TODO: catch error and report it ignore_matches.remove(p) if ignore_matches: return True diff --git a/tests-ng/compare-negative-ignore.sh b/tests-ng/compare-negative-ignore.sh new file mode 100755 index 0000000..a25f115 --- /dev/null +++ b/tests-ng/compare-negative-ignore.sh @@ -0,0 +1,132 @@ +#!/usr/bin/env bash +# author: jtt9340 (https://github.com/jtt9340) +# +# test install negative ignore absolute/relative +# returns 1 in case of error +# + +# exit on first error +#set -e + +# all this crap to get current path +if [ $(uname) = Darwin ]; then + # Unfortunately, readlink works differently on macOS than it does on GNU/Linux + # (the -f option behaves differently) and the realpath command does not exist. + # Workarounds I find on the Internet suggest just using Homebrew to install coreutils + # so you can get the GNU coreutils on your Mac. But, I don't want this script to + # assume (a) users have Homebrew installed and (b) if they have Homebrew installed, that + # they then installed the GNU coreutils. + readlink() { + TARGET_FILE=$1 + + cd `dirname $TARGET_FILE` + TARGET_FILE=`basename $TARGET_FILE` + + # Iterate down a (possible) chain of symlinks + while [ -L "$TARGET_FILE" ]; do + TARGET_FILE=`readlink $TARGET_FILE` + cd `dirname $TARGET_FILE` + TARGET_FILE=`basename $TARGET_FILE` + done + + # Compute the canonicalized name by finding the physical path + # for the directory we're in and appending the target file. + PHYS_DIR=`pwd -P` + RESULT=$PHYS_DIR/$TARGET_FILE + echo $RESULT + } + rl="readlink" +else + rl="readlink -f" + if ! ${rl} "${0}" >/dev/null 2>&1; then + rl="realpath" + + if ! hash ${rl}; then + echo "\"${rl}\" not found !" && exit 1 + fi + fi +fi +cur=$(dirname "$(${rl} "${0}")") + +# dotdrop path can be pass as argument +ddpath="${cur}/../" +[ "${1}" != "" ] && ddpath="${1}" +[ ! -d ${ddpath} ] && echo "ddpath \"${ddpath}\" is not a directory" && exit 1 + +export PYTHONPATH="${ddpath}:${PYTHONPATH}" +bin="python3 -m dotdrop.dotdrop" + +echo "dotdrop path: ${ddpath}" +echo "pythonpath: ${PYTHONPATH}" + +# get the helpers +source ${cur}/helpers + +echo -e "$(tput setaf 6)==> RUNNING $(basename $BASH_SOURCE) <==$(tput sgr0)" + +################################################################ +# this is the test +################################################################ + +# dotdrop directory +basedir=`mktemp -d --suffix='-dotdrop-tests' 2>/dev/null || mktemp -d` +echo "[+] dotdrop dir: ${basedir}" +echo "[+] dotpath dir: ${basedir}/dotfiles" + +# the dotfile to be imported +tmpd=`mktemp -d --suffix='-dotdrop-tests' 2>/dev/null || mktemp -d` + +# some files +mkdir -p ${tmpd}/program/ignore_me +echo "some data" > ${tmpd}/program/a +echo "some data" > ${tmpd}/program/ignore_me/b +echo "some data" > ${tmpd}/program/ignore_me/c + +# create the config file +cfg="${basedir}/config.yaml" +create_conf ${cfg} # sets token + +# import +echo "[+] import" +cd ${ddpath} | ${bin} import -c ${cfg} ${tmpd}/program + +# make some changes to generate a diff +echo "some other data" > ${tmpd}/program/a +echo "some other data" > ${tmpd}/program/ignore_me/b +echo "some other data" > ${tmpd}/program/ignore_me/c + +echo "[+] comparing normal - 3 diffs" +set +e +cd ${ddpath} | ${bin} compare -c ${cfg} --verbose +[ "$?" = 0 ] && exit 1 # We don't want an exit status of 0 +set -e + +# expects two diffs +patt0="*/ignore_me/*" +patt1="!*/ignore_me/c" +echo "[+] comparing with ignore (patterns: ${patt0} and ${patt1}) - 2 diffs" +set +e +cd ${ddpath} | ${bin} compare -c ${cfg} --verbose --ignore=${patt0} --ignore=${patt1} +[ "$?" = "0" ] && exit 1 +set -e + +# Adding ignore in dotfile +cfg2="${basedir}/config2.yaml" +sed '/d_program:/a\ +\ \ \ \ cmpignore:\ +\ \ \ \ - "*/ignore_me/*"\ +\ \ \ \ - "!*/ignore_me/c" +' ${cfg} > ${cfg2} + +# still expects two diffs +echo "[+] comparing with ignore in dotfile - 2 diffs" +set +e +cd ${ddpath} | ${bin} compare -c ${cfg2} --verbose +[ "$?" = "0" ] && exit 1 +set -e + +# clean +rm -rf ${basedir} ${tmpd} + +echo "OK" +exit 0 \ No newline at end of file From a73cfc2ef7ded9aa3477b38620215528cd3f1f74 Mon Sep 17 00:00:00 2001 From: Joey Territo Date: Tue, 15 Dec 2020 21:34:30 -0500 Subject: [PATCH 05/19] Fix unit test for negative ignore patterns in install command On macOS, creating a temporary directory creates a hard link to a file with a different name, which causes dotdrop to think it is importing a symlink. This causes the unit tests to fail as dotdrop attempts to ask the user if it should dereference the symlink, but dotdrop is not connected to stdin when the shell script tests are running so dotdrop crashes with an EOFError. The solution is to pass the -f flag when invoking `dotdrop import` in tests. --- tests-ng/install-negative-ignore.sh | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests-ng/install-negative-ignore.sh b/tests-ng/install-negative-ignore.sh index f6b7fa2..4eacef0 100755 --- a/tests-ng/install-negative-ignore.sh +++ b/tests-ng/install-negative-ignore.sh @@ -6,7 +6,7 @@ # # exit on first error -#set -e +set -e # all this crap to get current path if [ $(uname) = Darwin ]; then @@ -55,6 +55,7 @@ ddpath="${cur}/../" export PYTHONPATH="${ddpath}:${PYTHONPATH}" bin="python3 -m dotdrop.dotdrop" +hash coverage 2>/dev/null && bin="converage run -a --source=dotdrop -m dotdrop.dotdrop" || true echo "dotdrop path: ${ddpath}" echo "pythonpath: ${PYTHONPATH}" @@ -88,7 +89,7 @@ create_conf ${cfg} # sets token # import echo "[+] import" -cd ${ddpath} | ${bin} import -c ${cfg} ${tmpd}/program +cd ${ddpath} | ${bin} import -f -c ${cfg} ${tmpd}/program # adding ignore in dotfile cfg2="${basedir}/config2.yaml" From b09651be74534d2ac8aa0ab357dcbc9e87fcf728 Mon Sep 17 00:00:00 2001 From: Joey Territo Date: Tue, 15 Dec 2020 22:02:58 -0500 Subject: [PATCH 06/19] Fix unit test for negative ignore patterns in compare command See explanation in commit a73cfc2 for explanation --- tests-ng/compare-negative-ignore.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests-ng/compare-negative-ignore.sh b/tests-ng/compare-negative-ignore.sh index a25f115..7c545f8 100755 --- a/tests-ng/compare-negative-ignore.sh +++ b/tests-ng/compare-negative-ignore.sh @@ -88,7 +88,7 @@ create_conf ${cfg} # sets token # import echo "[+] import" -cd ${ddpath} | ${bin} import -c ${cfg} ${tmpd}/program +cd ${ddpath} | ${bin} import -f -c ${cfg} ${tmpd}/program # make some changes to generate a diff echo "some other data" > ${tmpd}/program/a From e1eb5f99c1bca098d6f114f02585e6d1c4e3f265 Mon Sep 17 00:00:00 2001 From: Joey Territo Date: Wed, 16 Dec 2020 18:10:02 -0500 Subject: [PATCH 07/19] Add unit tests for relative negative ignore pattern in compare command --- tests-ng/compare-negative-ignore-relative.sh | 152 +++++++++++++++++++ 1 file changed, 152 insertions(+) create mode 100755 tests-ng/compare-negative-ignore-relative.sh diff --git a/tests-ng/compare-negative-ignore-relative.sh b/tests-ng/compare-negative-ignore-relative.sh new file mode 100755 index 0000000..ba8cfc9 --- /dev/null +++ b/tests-ng/compare-negative-ignore-relative.sh @@ -0,0 +1,152 @@ +#!/usr/bin/env bash +# author: jtt9340 (https://github.com/jtt9340) +# +# test compare negative ignore relative +# returns 1 in case of error +# + +# exit on first error +set -e + +# all this crap to get current path +if [ $(uname) = Darwin ]; then + # Unfortunately, readlink works differently on macOS than it does on GNU/Linux + # (the -f option behaves differently) and the realpath command does not exist. + # Workarounds I find on the Internet suggest just using Homebrew to install coreutils + # so you can get the GNU coreutils on your Mac. But, I don't want this script to + # assume (a) users have Homebrew installed and (b) if they have Homebrew installed, that + # they then installed the GNU coreutils. + readlink() { + TARGET_FILE=$1 + + cd `dirname $TARGET_FILE` + TARGET_FILE=`basename $TARGET_FILE` + + # Iterate down a (possible) chain of symlinks + while [ -L "$TARGET_FILE" ]; do + TARGET_FILE=`readlink $TARGET_FILE` + cd `dirname $TARGET_FILE` + TARGET_FILE=`basename $TARGET_FILE` + done + + # Compute the canonicalized name by finding the physical path + # for the directory we're in and appending the target file. + PHYS_DIR=`pwd -P` + RESULT=$PHYS_DIR/$TARGET_FILE + echo $RESULT + } + rl="readlink" +else + rl="readlink -f" + if ! ${rl} "${0}" >/dev/null 2>&1; then + rl="realpath" + + if ! hash ${rl}; then + echo "\"${rl}\" not found !" && exit 1 + fi + fi +fi +cur=$(dirname "$(${rl} "${0}")") + +# dotdrop can be pass as argument +ddpath="${cur}/../" +[ "${1}" != "" ] && ddpath="${1}" +[ ! -d ${ddpath} ] && echo "ddpath \"${ddpath}\" is not a directory" && exit 1 + +export PYTHONPATH="${ddpath}:${PYTHONPATH}" +bin="python3 -m dotdrop.dotdrop" +hash coverage 2>/dev/null && bin="coverage run -a --source=dotdrop -m dotdrop.dotdrop" || true + +echo "dotdrop path: ${ddpath}" +echo "pythonpath: ${PYTHONPATH}" + +# get the helpers +source ${cur}/helpers + +echo -e "$(tput setaf 6)==> RUNNING $(basename $BASH_SOURCE) <==$(tput sgr0)" + +################################################################ +# this is the test +################################################################ + +# dotdrop directory +basedir=`mktemp -d --suffix='-dotdrop-tests' 2>/dev/null || mktemp -d` +echo "[+] dotdrop dir: ${basedir}" +echo "[+] dotpath dir: ${basedir}/dotfiles" + +# the dotfile to be imported +tmpd=`mktemp -d --suffix='-dotdrop-tests' 2>/dev/null || mktemp -d` + +# some files +mkdir -p ${tmpd}/program/ignore_me +echo "some data" > ${tmpd}/program/a +echo "some data" > ${tmpd}/program/ignore_me/b +echo "some data" > ${tmpd}/program/ignore_me/c + +# create the config file +cfg="${basedir}/config.yaml" +create_conf ${cfg} # sets token + +# import +echo "[+] import" +cd ${ddpath} | ${bin} import -f -c ${cfg} ${tmpd}/program + +# make some changes to generate a diff +echo "some other data" > ${tmpd}/program/a +echo "some other data" > ${tmpd}/program/ignore_me/b +echo "some other data" > ${tmpd}/program/ignore_me/c + +# expects two diffs (no need to test comparing normal - 3 diffs, as that is taken care of in compare-negative-ignore.sh) +patt0="ignore_me/*" +patt1="!ignore_me/c" +echo "[+] comparing with ignore (patterns: ${patt0} and ${patt1}) - 2 diffs" +set +e +cd ${ddpath} | ${bin} compare -c ${cfg} --verbose --ignore=${patt0} --ignore=${patt1} +[ "$?" = "0" ] && exit 1 +set -e + +######################################## +# Test ignores specified in config.yaml +######################################## +# add some files +mkdir -p ${tmpd}/.zsh +echo "some data" > ${tmpd}/.zsh/somefile +mkdir -p ${tmpd}/.zsh/plugins +echo "some data" > ${tmpd}/.zsh/plugins/someplugin + +echo "[+] import .zsh" +cd ${ddpath} | ${bin} import -f -c ${cfg} ${tmpd}/.zsh + +touch ${tmpd}/.zsh/plugins/ignore-1.zsh +touch ${tmpd}/.zsh/plugins/ignore-2.zsh + +# adding ignore in config.yaml +cfg2="${basedir}/config2.yaml" +sed '/d_zsh:/a\ +\ \ \ \ cmpignore:\ +\ \ \ \ - "plugins/ignore-?.zsh"\ +\ \ \ \ - "!plugins/ignore-2.zsh" +' ${cfg} > ${cfg2} + +# expects one diff +patt0="plugins/ignore-?.zsh" +patt1="!plugins/ignore-2.zsh" +echo "[+] comparing with ignore (patterns: ${patt0} and ${patt1}) - 1 diff" +set +e +cd ${ddpath} | ${bin} compare -c ${cfg} --verbose -C ${tmpd}/.zsh --ignore=${patt0} --ignore=${patt1} +[ "$?" = "0" ] && exit 1 +set -e + +# expects one diff +echo "[+] comparing .zsh with ignore in dotfile - 1 diff expected" +set +e +cd ${ddpath} | ${bin} compare -c ${cfg2} --verbose -C ${tmpd}/.zsh +ret="$?" +echo ${ret} +[ "${ret}" = "0" ] && exit 1 +set -e + +# clean +rm -rf ${basedir} ${tmpd} + +echo "OK" From dce19eb268b1032c1e9c68ef7505c9efa70a93e1 Mon Sep 17 00:00:00 2001 From: Joey Territo Date: Fri, 18 Dec 2020 18:54:42 -0500 Subject: [PATCH 08/19] Add logic for respecting ignores/negative ignores when updating files --- dotdrop/updater.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/dotdrop/updater.py b/dotdrop/updater.py index 37c6dd0..de38e21 100644 --- a/dotdrop/updater.py +++ b/dotdrop/updater.py @@ -8,6 +8,7 @@ handle the update of dotfiles import os import shutil import filecmp +import fnmatch # local imports from dotdrop.logger import Logger @@ -273,8 +274,22 @@ class Updater: continue if self.debug: self.log.dbg('cp -r {} {}'.format(exist, new)) + # Newly created directory should be copied as is (for efficiency). - shutil.copytree(exist, new) + def ig(src, names): + whitelist, blacklist = set(), set() + for ignore in self.ignores: + for name in names: + path = os.path.join(src, name) + if ignore.startswith('!') and fnmatch.fnmatch(path, ignore[1:]): + # add to whitelist + whitelist.add(name) + elif fnmatch.fnmatch(path, ignore): + # add to blacklist + blacklist.add(name) + return blacklist - whitelist + + shutil.copytree(exist, new, ignore=ig) self.log.sub('\"{}\" dir added'.format(new)) # remove dirs that don't exist in deployed version From 39ff71b5c2a664a76dbc0ce77db91572b9fcc0f8 Mon Sep 17 00:00:00 2001 From: Joey Territo Date: Fri, 18 Dec 2020 18:55:20 -0500 Subject: [PATCH 09/19] Add unit test for negative ignore patterns in update command --- tests-ng/update-negative-ignore.sh | 126 +++++++++++++++++++++++++++++ 1 file changed, 126 insertions(+) create mode 100755 tests-ng/update-negative-ignore.sh diff --git a/tests-ng/update-negative-ignore.sh b/tests-ng/update-negative-ignore.sh new file mode 100755 index 0000000..cbe839a --- /dev/null +++ b/tests-ng/update-negative-ignore.sh @@ -0,0 +1,126 @@ +#!/usr/bin/env bash +# author: jtt9340 (https://github.com/jtt9340) +# +# test negative ignore update +# returns 1 in case of error +# + +# exit on first error +set -e + +# all this crap to get current path +if [ $(uname) = Darwin ]; then + # Unfortunately, readlink works differently on macOS than it does on GNU/Linux + # (the -f option behaves differently) and the realpath command does not exist. + # Workarounds I find on the Internet suggest just using Homebrew to install coreutils + # so you can get the GNU coreutils on your Mac. But, I don't want this script to + # assume (a) users have Homebrew installed and (b) if they have Homebrew installed, that + # they then installed the GNU coreutils. + readlink() { + TARGET_FILE=$1 + + cd `dirname $TARGET_FILE` + TARGET_FILE=`basename $TARGET_FILE` + + # Iterate down a (possible) chain of symlinks + while [ -L "$TARGET_FILE" ]; do + TARGET_FILE=`readlink $TARGET_FILE` + cd `dirname $TARGET_FILE` + TARGET_FILE=`basename $TARGET_FILE` + done + + # Compute the canonicalized name by finding the physical path + # for the directory we're in and appending the target file. + PHYS_DIR=`pwd -P` + RESULT=$PHYS_DIR/$TARGET_FILE + echo $RESULT + } + rl="readlink" +else + rl="readlink -f" + if ! ${rl} "${0}" >/dev/null 2>&1; then + rl="realpath" + + if ! hash ${rl}; then + echo "\"${rl}\" not found !" && exit 1 + fi + fi +fi +cur=$(dirname "$(${rl} "${0}")") + +#hash dotdrop >/dev/null 2>&1 +#[ "$?" != "0" ] && echo "install dotdrop to run tests" && exit 1 + +#echo "called with ${1}" + +# dotdrop can be pass as argument +ddpath="${cur}/../" +[ "${1}" != "" ] && ddpath="${1}" +[ ! -d ${ddpath} ] && exho "ddpath \"${ddpath}\" is not a directory" && exit 1 + +export PYTHONPATH="${ddpath}:${PYTHONPATH}" +bin="python3 -m dotdrop.dotdrop" +hash coverage 2>/dev/null && bin="coverage run -a --source=dotdrop -m dotdrop.dotdrop" || true + +echo "dotdrop path: ${ddpath}" +echo "pythonpath: ${PYTHONPATH}" + +# get the helpers +source ${cur}/helpers + +echo -e "$(tput setaf 6)==> RUNNING $(basename $BASH_SOURCE) <==$(tput sgr0)" + +################################################################ +# this is the test +################################################################ +# dotdrop directory +basedir=`mktemp -d --suffix='-dotdrop-tests' 2>/dev/null || mktemp -d` +echo "[+] dotdrop dir: ${basedir}" +echo "[+] dotpath dir: ${basedir}/dotfiles" +mkdir -p ${basedir}/dotfiles/a/{b,c} +echo 'a' > ${basedir}/dotfiles/a/b/abfile +echo 'a' > ${basedir}/dotfiles/a/c/acfile + +# the dotfile to be updated +tmpd=`mktemp -d --suffix='-dotdrop-tests' 2>/dev/null || mktemp -d` +cp -r ${basedir}/dotfiles/a ${tmpd}/ + +# create the config file +cfg="${basedir}/config.yaml" +cat > ${cfg} << _EOF +config: + backup: false + create: true + dotpath: dotfiles +dotfiles: + f_abc: + dst: ${tmpd}/a + src: a + upignore: + - "*/newdir/b/*" + - "!*/newdir/b/d" +profiles: + p1: + dotfiles: + - f_abc +_EOF + +# edit/add files +echo "[+] edit/add files" +mkdir -p ${tmpd}/a/newdir/b +touch ${tmpd}/a/newdir/b/{c,d} + +# update +echo "[+] update" +cd ${ddpath} | ${bin} update -f -c ${cfg} --verbose --profile=p1 --key f_abc + +# check files haven't been updated +lsd --tree ${basedir} +[ -e ${basedir}/dotfiles/a/newdir/b/c ] && echo "should not have been updated" && exit 1 +[ ! -e ${basedir}/dotfiles/a/newdir/b/d ] && echo "should have been updated" && exit 1 + +## CLEANING +rm -rf ${basedir} ${tmpd} + +echo "OK" +exit 0 From 4d827e8d3d6832e7a7470de517aa7be1052eb747 Mon Sep 17 00:00:00 2001 From: Joey Territo Date: Sun, 20 Dec 2020 14:03:38 -0500 Subject: [PATCH 10/19] Add more test cases to the update-negative-ignore test --- tests-ng/update-negative-ignore.sh | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/tests-ng/update-negative-ignore.sh b/tests-ng/update-negative-ignore.sh index cbe839a..29b62b5 100755 --- a/tests-ng/update-negative-ignore.sh +++ b/tests-ng/update-negative-ignore.sh @@ -73,12 +73,24 @@ echo -e "$(tput setaf 6)==> RUNNING $(basename $BASH_SOURCE) <==$(tput sgr0)" ################################################################ # this is the test ################################################################ + +# $1 pattern +# $2 path +grep_or_fail() +{ + set +e + grep "${1}" "${2}" >/dev/null 2>&1 || (echo "pattern not found in ${2}" && exit 1) + set -e +} + # dotdrop directory basedir=`mktemp -d --suffix='-dotdrop-tests' 2>/dev/null || mktemp -d` echo "[+] dotdrop dir: ${basedir}" echo "[+] dotpath dir: ${basedir}/dotfiles" mkdir -p ${basedir}/dotfiles/a/{b,c} -echo 'a' > ${basedir}/dotfiles/a/b/abfile +echo 'a' > ${basedir}/dotfiles/a/b/abfile1 +echo 'a' > ${basedir}/dotfiles/a/b/abfile2 +echo 'a' > ${basedir}/dotfiles/a/b/abfile3 echo 'a' > ${basedir}/dotfiles/a/c/acfile # the dotfile to be updated @@ -99,6 +111,8 @@ dotfiles: upignore: - "*/newdir/b/*" - "!*/newdir/b/d" + - "*/abfile?" + - "!*/abfile3" profiles: p1: dotfiles: @@ -108,6 +122,10 @@ _EOF # edit/add files echo "[+] edit/add files" mkdir -p ${tmpd}/a/newdir/b +echo 'b' > ${tmpd}/a/b/abfile1 +echo 'b' > ${tmpd}/a/b/abfile2 +echo 'b' > ${tmpd}/a/b/abfile3 +echo 'b' > ${tmpd}/a/b/abfile4 touch ${tmpd}/a/newdir/b/{c,d} # update @@ -115,9 +133,12 @@ echo "[+] update" cd ${ddpath} | ${bin} update -f -c ${cfg} --verbose --profile=p1 --key f_abc # check files haven't been updated -lsd --tree ${basedir} -[ -e ${basedir}/dotfiles/a/newdir/b/c ] && echo "should not have been updated" && exit 1 -[ ! -e ${basedir}/dotfiles/a/newdir/b/d ] && echo "should have been updated" && exit 1 +grep_or_fail a ${basedir}/dotfiles/a/b/abfile1 +grep_or_fail a ${basedir}/dotfiles/a/b/abfile2 +grep_or_fail b ${basedir}/dotfiles/a/b/abfile3 +[ -e ${basedir}/dotfiles/a/b/abfile4 ] && echo "abfile4 should not have been updated" && exit 1 +[ -e ${basedir}/dotfiles/a/newdir/b/c ] && echo "newdir/b/c should not have been updated" && exit 1 +[ ! -e ${basedir}/dotfiles/a/newdir/b/d ] && echo "newdir/b/d should have been updated" && exit 1 ## CLEANING rm -rf ${basedir} ${tmpd} From c0a192ea128f7a7d220ada320f6701de72315b96 Mon Sep 17 00:00:00 2001 From: Joey Territo Date: Mon, 21 Dec 2020 17:39:43 -0500 Subject: [PATCH 11/19] Fix typo in install-negative-ignore.sh: converage -> coverage --- tests-ng/install-negative-ignore.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests-ng/install-negative-ignore.sh b/tests-ng/install-negative-ignore.sh index 4eacef0..4c2beef 100755 --- a/tests-ng/install-negative-ignore.sh +++ b/tests-ng/install-negative-ignore.sh @@ -55,7 +55,7 @@ ddpath="${cur}/../" export PYTHONPATH="${ddpath}:${PYTHONPATH}" bin="python3 -m dotdrop.dotdrop" -hash coverage 2>/dev/null && bin="converage run -a --source=dotdrop -m dotdrop.dotdrop" || true +hash coverage 2>/dev/null && bin="coverage run -a --source=dotdrop -m dotdrop.dotdrop" || true echo "dotdrop path: ${ddpath}" echo "pythonpath: ${PYTHONPATH}" From 4cd6d3bd242cae0837846166e2c1af86b2388220 Mon Sep 17 00:00:00 2001 From: Joey Territo Date: Mon, 21 Dec 2020 18:08:53 -0500 Subject: [PATCH 12/19] Add unit test for global negative ignore patterns in compare command --- tests-ng/global-compare-negative-ignore.sh | 119 +++++++++++++++++++++ 1 file changed, 119 insertions(+) create mode 100755 tests-ng/global-compare-negative-ignore.sh diff --git a/tests-ng/global-compare-negative-ignore.sh b/tests-ng/global-compare-negative-ignore.sh new file mode 100755 index 0000000..a6b4a13 --- /dev/null +++ b/tests-ng/global-compare-negative-ignore.sh @@ -0,0 +1,119 @@ +#!/usr/bin/env bash +# author: jtt9340 (https://github.com/jtt9340) +# +# test install cmpignore with negative ignores globally +# returns 1 in case of error +# + +# exit on first error +set -e + +# all this crap to get current path +if [ $(uname) = Darwin ]; then + # Unfortunately, readlink works differently on macOS than it does on GNU/Linux + # (the -f option behaves differently) and the realpath command does not exist. + # Workarounds I find on the Internet suggest just using Homebrew to install coreutils + # so you can get the GNU coreutils on your Mac. But, I don't want this script to + # assume (a) users have Homebrew installed and (b) if they have Homebrew installed, that + # they then installed the GNU coreutils. + readlink() { + TARGET_FILE=$1 + + cd `dirname $TARGET_FILE` + TARGET_FILE=`basename $TARGET_FILE` + + # Iterate down a (possible) chain of symlinks + while [ -L "$TARGET_FILE" ]; do + TARGET_FILE=`readlink $TARGET_FILE` + cd `dirname $TARGET_FILE` + TARGET_FILE=`basename $TARGET_FILE` + done + + # Compute the canonicalized name by finding the physical path + # for the directory we're in and appending the target file. + PHYS_DIR=`pwd -P` + RESULT=$PHYS_DIR/$TARGET_FILE + echo $RESULT + } + rl="readlink" +else + rl="readlink -f" + if ! ${rl} "${0}" >/dev/null 2>&1; then + rl="realpath" + + if ! hash ${rl}; then + echo "\"${rl}\" not found !" && exit 1 + fi + fi +fi +cur=$(dirname "$(${rl} "${0}")") + +# dotdrop path can be pass as argument +ddpath="${cur}/../" +[ "${1}" != "" ] && ddpath="${1}" +[ ! -d ${ddpath} ] && echo "ddpath \"{ddpath}\" is not a directory" && exit 1 + +export PYTHONPATH="${ddpath}:${PYTHONPATH}" +bin="python3 -m dotdrop.dotdrop" +hash coverage 2>/dev/null && bin="coverage run -a --source=dotdrop -m dotdrop.dotdrop" || true + +echo "dotdrop path: ${ddpath}" +echo "pythonpath: ${PYTHONPATH}" + +# get the helpers +source ${cur}/helpers + +echo -e "$(tput setaf 6)==> RUNNING $(basename $BASH_SOURCE) <==$(tput sgr0)" + +################################################################ +# this is the test +################################################################ + +# dotdrop directory +basedir=`mktemp -d --suffix='-dotdrop-tests' 2>/dev/null || mktemp -d` +echo "[+] dotdrop dir: ${basedir}" +echo "[+] dotpath dir: ${basedir}/dotfiles" + +# the dotfile to be imported +tmpd=`mktemp -d --suffix='-dotdrop-tests' 2>/dev/null || mktemp -d` + +# some files +mkdir -p ${tmpd}/{program,config} +touch ${tmpd}/program/a +touch ${tmpd}/config/a + +# create the config file +cfg="${basedir}/config.yaml" +create_conf ${cfg} # sets token + +# import +echo "[+] import" +cd ${ddpath} | ${bin} import -f -c ${cfg} ${tmpd}/program +cd ${ddpath} | ${bin} import -f -c ${cfg} ${tmpd}/config + +# add files +echo "[+] add files" +touch ${tmpd}/program/b +touch ${tmpd}/config/b + +# adding ignore in dotfile +cfg2="${basedir}/config2.yaml" +sed '/dotpath: dotfiles/a\ +\ \ cmpignore:\ +\ \ \ \ - "*/config/*"\ +\ \ \ \ - "!*/config/a"\ +' ${cfg} > ${cfg2} +cat ${cfg2} + +# expects one diff +echo "[+] comparing with ignore in dotfile - 1 diff" +set +e +cd ${ddpath} | ${bin} compare -c ${cfg2} --verbose +[ "$?" = "0" ] && exit 1 +set -e + +## CLEANING +rm -rf ${basedir} ${tmpd} + +echo "OK" +exit 0 From f694cdcbb72b67f15b6388be12bbdc5273e0cc9d Mon Sep 17 00:00:00 2001 From: Joey Territo Date: Mon, 21 Dec 2020 20:30:47 -0500 Subject: [PATCH 13/19] Add unit test for global negative ignore patterns in update command --- tests-ng/compare-negative-ignore-relative.sh | 2 +- tests-ng/global-update-negative-ignore.sh | 135 +++++++++++++++++++ tests-ng/update-negative-ignore.sh | 2 +- 3 files changed, 137 insertions(+), 2 deletions(-) create mode 100755 tests-ng/global-update-negative-ignore.sh diff --git a/tests-ng/compare-negative-ignore-relative.sh b/tests-ng/compare-negative-ignore-relative.sh index ba8cfc9..7d9833b 100755 --- a/tests-ng/compare-negative-ignore-relative.sh +++ b/tests-ng/compare-negative-ignore-relative.sh @@ -48,7 +48,7 @@ else fi cur=$(dirname "$(${rl} "${0}")") -# dotdrop can be pass as argument +# dotdrop path can be pass as argument ddpath="${cur}/../" [ "${1}" != "" ] && ddpath="${1}" [ ! -d ${ddpath} ] && echo "ddpath \"${ddpath}\" is not a directory" && exit 1 diff --git a/tests-ng/global-update-negative-ignore.sh b/tests-ng/global-update-negative-ignore.sh new file mode 100755 index 0000000..268b812 --- /dev/null +++ b/tests-ng/global-update-negative-ignore.sh @@ -0,0 +1,135 @@ +#!/usr/bin/env bash +# author: jtt9340 (https://github.com/jtt9340) +# +# test global negative ignore update +# returns 1 in case of error +# + +# exit on first error +set -e + +# all this crap to get current path +if [ $(uname) = Darwin ]; then + # Unfortunately, readlink works differently on macOS than it does on GNU/Linux + # (the -f option behaves differently) and the realpath command does not exist. + # Workarounds I find on the Internet suggest just using Homebrew to install coreutils + # so you can get the GNU coreutils on your Mac. But, I don't want this script to + # assume (a) users have Homebrew installed and (b) if they have Homebrew installed, that + # they then installed the GNU coreutils. + readlink() { + TARGET_FILE=$1 + + cd `dirname $TARGET_FILE` + TARGET_FILE=`basename $TARGET_FILE` + + # Iterate down a (possible) chain of symlinks + while [ -L "$TARGET_FILE" ]; do + TARGET_FILE=`readlink $TARGET_FILE` + cd `dirname $TARGET_FILE` + TARGET_FILE=`basename $TARGET_FILE` + done + + # Compute the canonicalized name by finding the physical path + # for the directory we're in and appending the target file. + PHYS_DIR=`pwd -P` + RESULT=$PHYS_DIR/$TARGET_FILE + echo $RESULT + } + rl="readlink" +else + rl="readlink -f" + if ! ${rl} "${0}" >/dev/null 2>&1; then + rl="realpath" + + if ! hash ${rl}; then + echo "\"${rl}\" not found !" && exit 1 + fi + fi +fi +cur=$(dirname "$(${rl} "${0}")") + +# dotdrop path can be pass as argument +ddpath="${cur}/../" +[ "${1}" != "" ] && ddpath="${1}" +[ ! -d ${ddpath} ] && echo "ddpath \"${ddpath}\" is not a directory" && exit 1 + +export PYTHONPATH="${ddpath}:${PYTHONPATH}" +bin="python3 -m dotdrop.dotdrop" +hash coverage 2>/dev/null && bin="coverage run -a --source=dotdrop -m dotdrop.dotdrop" || true + +echo "dotdrop path: ${ddpath}" +echo "pythonpath: ${PYTHONPATH}" + +# get the helpers +source ${cur}/helpers + +echo -e "$(tput setaf 6)==> RUNNING $(basename $BASH_SOURCE) <==$(tput sgr0)" + +################################################################ +# this is the test +################################################################ + +# dotdrop directory +basedir=`mktemp -d --suffix='-dotdrop-tests' 2>/dev/null || mktemp -d` +echo "[+] dotdrop dir: ${basedir}" +echo "[+] dotpath dir: ${basedir}/dotfiles" +mkdir -p ${basedir}/dotfiles/a/{b,c} +echo 'a' > ${basedir}/dotfiles/a/b/abfile1 +echo 'a' > ${basedir}/dotfiles/a/b/abfile2 +echo 'a' > ${basedir}/dotfiles/a/b/abfile3 +echo 'a' > ${basedir}/dotfiles/a/c/acfile + +# the dotfile to be updated +tmpd=`mktemp -d --suffix='-dotdrop-tests' 2>/dev/null || mktemp -d` +cp -r ${basedir}/dotfiles/a ${tmpd}/ + +# create the config file +cfg="${basedir}/config.yaml" +cat > ${cfg} << _EOF +config: + backup: false + create: true + dotpath: dotfiles + upignore: + - "*/newdir/b/*" + - "!*/newdir/b/d" + - "*/abfile?" + - "!*/abfile3" +dotfiles: + f_abc: + dst: ${tmpd}/a + src: a +profiles: + p1: + dotfiles: + - f_abc +_EOF + +# edit/add files +echo "[+] edit/add files" +mkdir -p ${tmpd}/a/newdir/b +echo 'b' > ${tmpd}/a/b/abfile1 +echo 'b' > ${tmpd}/a/b/abfile2 +echo 'b' > ${tmpd}/a/b/abfile3 +echo 'b' > ${tmpd}/a/b/abfile4 +touch ${tmpd}/a/newdir/b/{c,d} + +# update +echo "[+] update" +cd ${ddpath} | ${bin} update -f -c ${cfg} --verbose --profile=p1 --key f_abc + +# check files haven't been updated +set +e +grep 'a' ${basedir}/dotfiles/a/b/abfile1 >/dev/null 2>&1 || (echo "abfile1 should not have been updated" && exit 1) +grep 'a' ${basedir}/dotfiles/a/b/abfile2 >/dev/null 2>&1 || (echo "abfile2 should not have been updated" && exit 1) +grep 'b' ${basedir}/dotfiles/a/b/abfile3 >/dev/null 2>&1 || (echo "abfile3 was not updated" && exit 1) +set -e +[ -e ${basedir}/dotfiles/a/b/abfile4 ] && echo "abfile4 should not have been updated" && exit 1 +[ -e ${basedir}/dotfiles/a/newdir/b/c ] && echo "newdir/b/c should not have been updated" && exit 1 +[ ! -e ${basedir}/dotfiles/a/newdir/b/d ] && echo "newdir/b/d should have been updated" && exit 1 + +## CLEANING +rm -rf ${basedir} ${tmpd} + +echo "OK" +exit 0 diff --git a/tests-ng/update-negative-ignore.sh b/tests-ng/update-negative-ignore.sh index 29b62b5..2c3e7ce 100755 --- a/tests-ng/update-negative-ignore.sh +++ b/tests-ng/update-negative-ignore.sh @@ -53,7 +53,7 @@ cur=$(dirname "$(${rl} "${0}")") #echo "called with ${1}" -# dotdrop can be pass as argument +# dotdrop path can be pass as argument ddpath="${cur}/../" [ "${1}" != "" ] && ddpath="${1}" [ ! -d ${ddpath} ] && exho "ddpath \"${ddpath}\" is not a directory" && exit 1 From 4d2f9bd285819a64d3b4757354ef519323f73218 Mon Sep 17 00:00:00 2001 From: Joey Territo Date: Tue, 22 Dec 2020 20:43:29 -0500 Subject: [PATCH 14/19] Add unit test for negative ignore patterns in import command --- tests-ng/compare-negative-ignore-relative.sh | 2 +- tests-ng/compare-negative-ignore.sh | 4 +- tests-ng/global-compare-negative-ignore.sh | 2 +- tests-ng/global-update-negative-ignore.sh | 2 +- tests-ng/import-negative-ignore.sh | 116 +++++++++++++++++++ tests-ng/install-negative-ignore.sh | 2 +- tests-ng/update-negative-ignore.sh | 2 +- 7 files changed, 123 insertions(+), 7 deletions(-) create mode 100755 tests-ng/import-negative-ignore.sh diff --git a/tests-ng/compare-negative-ignore-relative.sh b/tests-ng/compare-negative-ignore-relative.sh index 7d9833b..9773c32 100755 --- a/tests-ng/compare-negative-ignore-relative.sh +++ b/tests-ng/compare-negative-ignore-relative.sh @@ -50,7 +50,7 @@ cur=$(dirname "$(${rl} "${0}")") # dotdrop path can be pass as argument ddpath="${cur}/../" -[ "${1}" != "" ] && ddpath="${1}" +[ -n "${1}" ] && ddpath="${1}" [ ! -d ${ddpath} ] && echo "ddpath \"${ddpath}\" is not a directory" && exit 1 export PYTHONPATH="${ddpath}:${PYTHONPATH}" diff --git a/tests-ng/compare-negative-ignore.sh b/tests-ng/compare-negative-ignore.sh index 7c545f8..24e6b38 100755 --- a/tests-ng/compare-negative-ignore.sh +++ b/tests-ng/compare-negative-ignore.sh @@ -50,7 +50,7 @@ cur=$(dirname "$(${rl} "${0}")") # dotdrop path can be pass as argument ddpath="${cur}/../" -[ "${1}" != "" ] && ddpath="${1}" +[ -n "${1}" ] && ddpath="${1}" [ ! -d ${ddpath} ] && echo "ddpath \"${ddpath}\" is not a directory" && exit 1 export PYTHONPATH="${ddpath}:${PYTHONPATH}" @@ -129,4 +129,4 @@ set -e rm -rf ${basedir} ${tmpd} echo "OK" -exit 0 \ No newline at end of file +exit 0 diff --git a/tests-ng/global-compare-negative-ignore.sh b/tests-ng/global-compare-negative-ignore.sh index a6b4a13..56ca367 100755 --- a/tests-ng/global-compare-negative-ignore.sh +++ b/tests-ng/global-compare-negative-ignore.sh @@ -50,7 +50,7 @@ cur=$(dirname "$(${rl} "${0}")") # dotdrop path can be pass as argument ddpath="${cur}/../" -[ "${1}" != "" ] && ddpath="${1}" +[ -n "${1}" ] && ddpath="${1}" [ ! -d ${ddpath} ] && echo "ddpath \"{ddpath}\" is not a directory" && exit 1 export PYTHONPATH="${ddpath}:${PYTHONPATH}" diff --git a/tests-ng/global-update-negative-ignore.sh b/tests-ng/global-update-negative-ignore.sh index 268b812..3598f0f 100755 --- a/tests-ng/global-update-negative-ignore.sh +++ b/tests-ng/global-update-negative-ignore.sh @@ -50,7 +50,7 @@ cur=$(dirname "$(${rl} "${0}")") # dotdrop path can be pass as argument ddpath="${cur}/../" -[ "${1}" != "" ] && ddpath="${1}" +[ -n "${1}" ] && ddpath="${1}" [ ! -d ${ddpath} ] && echo "ddpath \"${ddpath}\" is not a directory" && exit 1 export PYTHONPATH="${ddpath}:${PYTHONPATH}" diff --git a/tests-ng/import-negative-ignore.sh b/tests-ng/import-negative-ignore.sh new file mode 100755 index 0000000..034deee --- /dev/null +++ b/tests-ng/import-negative-ignore.sh @@ -0,0 +1,116 @@ +#!/usr/bin/env bash +# author: jtt9340 (https://github.com/jtt9340) +# +# test negative ignore import +# returns 1 in case of error +# + +# exit on first error +set -e + +# all this crap to get current path +if [ $(uname) = Darwin ]; then + # Unfortunately, readlink works differently on macOS than it does on GNU/Linux + # (the -f option behaves differently) and the realpath command does not exist. + # Workarounds I find on the Internet suggest just using Homebrew to install coreutils + # so you can get the GNU coreutils on your Mac. But, I don't want this script to + # assume (a) users have Homebrew installed and (b) if they have Homebrew installed, that + # they then installed the GNU coreutils. + readlink() { + TARGET_FILE=$1 + + cd `dirname $TARGET_FILE` + TARGET_FILE=`basename $TARGET_FILE` + + # Iterate down a (possible) chain of symlinks + while [ -L "$TARGET_FILE" ]; do + TARGET_FILE=`readlink $TARGET_FILE` + cd `dirname $TARGET_FILE` + TARGET_FILE=`basename $TARGET_FILE` + done + + # Compute the canonicalized name by finding the physical path + # for the directory we're in and appending the target file. + PHYS_DIR=`pwd -P` + RESULT=$PHYS_DIR/$TARGET_FILE + echo $RESULT + } + rl="readlink" +else + rl="readlink -f" + if ! ${rl} "${0}" >/dev/null 2>&1; then + rl="realpath" + + if ! hash ${rl}; then + echo "\"${rl}\" not found !" && exit 1 + fi + fi +fi +cur=$(dirname "$(${rl} "${0}")") + +# dotdrop path can be pass as argument +ddpath="${cur}/../" +[ -n "${1}" ] && ddpath="${1}" +[ ! -d ${ddpath} ] && echo "ddpath \"${ddpath}\" is not a directory" && exit 1 + +export PYTHONPATH="${ddpath}:${PYTHONPATH}" +bin="python3 -m dotdrop.dotdrop" +hash coverage 2>/dev/null && bin="coverage run -a --source=dotdrop -m dotdrop.dotdrop" || true + +echo "dotdrop path: ${ddpath}" +echo "pythonpath: ${PYTHONPATH}" + +# get the helpers +source ${cur}/helpers + +echo -e "$(tput setaf 6)==> RUNNING $(basename $BASH_SOURCE) <==$(tput sgr0)" + +################################################################ +# this is the test +################################################################ +# the dotfile source +basedir=`mktemp -d --suffix='-dotdrop-tests' 2>/dev/null || mktemp -d` +mkdir -p ${basedir}/dotfiles + +# the dotfile destination +tmpd=`mkdir -d --suffix='-dotdrop-tests' 2>/dev/null || mktemp -d` + +# dotdrop directory +echo "[+] dotdrop dir: ${basedir}" +echo "[+] dotpath dir: ${basedir}/dotfiles" +mkdir -p ${tmpd}/a/{b,c} +echo 'a' > ${tmpd}/a/b/abfile1 +echo 'a' > ${tmpd}/a/b/abfile2 +echo 'a' > ${tmpd}/a/b/abfile3 +echo 'a' > ${tmpd}/a/c/acfile +mkdir -p ${tmpd}/a/newdir/b +touch ${tmpd}/a/newdir/b/{c,d} + +# create the config file +cfg="${basedir}/config.yaml" +cfg2="${basedir}/config2.yaml" +create_conf ${cfg} # sets token +sed '/dotpath: dotfiles/a\ +\ \ impignore:\ +\ \ \ \ - "*/newdir/b/*"\ +\ \ \ \ - "!*/newdir/b/d"\ +\ \ \ \ - "*/abfile?"\ +\ \ \ \ - "!*/abfile3" +' ${cfg} > ${cfg2} + +# import +echo "[+] import" +cd ${ddpath} | ${bin} import -f -c ${cfg2} --verbose --profile=p1 ${tmpd}/a --as=~/a + +# check files haven't been imported +[ -e ${basedir}/dotfiles/a/newdir/b/c ] && echo "newdir/b/c should not have been imported" && exit 1 +[ ! -e ${basedir}/dotfiles/a/newdir/b/d ] && echo "newdir/b/d should have been imported" && exit 1 +[ -e ${basedir}/dotfiles/a/b/abfile1 ] && echo "abfile1 should not have been imported" && exit 1 +[ -e ${basedir}/dotfiles/a/b/abfile2 ] && echo "abfile2 should not have been imported" && exit 1 +[ ! -e ${basedir}/dotfiles/a/b/abfile3 ] && echo "abfile3 should have been imported" && exit 1 + +## CLEANING +rm -rf ${tmpd} ${basedir} + +echo "OK" +exit 0 diff --git a/tests-ng/install-negative-ignore.sh b/tests-ng/install-negative-ignore.sh index 4c2beef..6fd6779 100755 --- a/tests-ng/install-negative-ignore.sh +++ b/tests-ng/install-negative-ignore.sh @@ -50,7 +50,7 @@ cur=$(dirname "$(${rl} "${0}")") # dotdrop path can be pass as argument ddpath="${cur}/../" -[ "${1}" != "" ] && ddpath="${1}" +[ -n "${1}" ] && ddpath="${1}" [ ! -d ${ddpath} ] && echo "ddpath \"${ddpath}\" is not a directory" && exit 1 export PYTHONPATH="${ddpath}:${PYTHONPATH}" diff --git a/tests-ng/update-negative-ignore.sh b/tests-ng/update-negative-ignore.sh index 2c3e7ce..125cfa0 100755 --- a/tests-ng/update-negative-ignore.sh +++ b/tests-ng/update-negative-ignore.sh @@ -55,7 +55,7 @@ cur=$(dirname "$(${rl} "${0}")") # dotdrop path can be pass as argument ddpath="${cur}/../" -[ "${1}" != "" ] && ddpath="${1}" +[ -n "${1}" ] && ddpath="${1}" [ ! -d ${ddpath} ] && exho "ddpath \"${ddpath}\" is not a directory" && exit 1 export PYTHONPATH="${ddpath}:${PYTHONPATH}" From 963a848b2bf0041cc3e536718d7b0e46edab296f Mon Sep 17 00:00:00 2001 From: Joey Territo Date: Tue, 22 Dec 2020 21:36:48 -0500 Subject: [PATCH 15/19] Add unit tests for relative negative ignore pattern in update command --- tests-ng/update-negative-ignore-relative.sh | 135 ++++++++++++++++++++ 1 file changed, 135 insertions(+) create mode 100755 tests-ng/update-negative-ignore-relative.sh diff --git a/tests-ng/update-negative-ignore-relative.sh b/tests-ng/update-negative-ignore-relative.sh new file mode 100755 index 0000000..0f9b281 --- /dev/null +++ b/tests-ng/update-negative-ignore-relative.sh @@ -0,0 +1,135 @@ +#!/usr/bin/env bash +# author: jtt9340 (https://github.com/jtt9340) +# +# test ignore update negative relative pattern +# returns 1 in case of error +# + +# exit on first error +set -e + +# all this crap to get current path +if [ $(uname) = Darwin ]; then + # Unfortunately, readlink works differently on macOS than it does on GNU/Linux + # (the -f option behaves differently) and the realpath command does not exist. + # Workarounds I find on the Internet suggest just using Homebrew to install coreutils + # so you can get the GNU coreutils on your Mac. But, I don't want this script to + # assume (a) users have Homebrew installed and (b) if they have Homebrew installed, that + # they then installed the GNU coreutils. + readlink() { + TARGET_FILE=$1 + + cd `dirname $TARGET_FILE` + TARGET_FILE=`basename $TARGET_FILE` + + # Iterate down a (possible) chain of symlinks + while [ -L "$TARGET_FILE" ]; do + TARGET_FILE=`readlink $TARGET_FILE` + cd `dirname $TARGET_FILE` + TARGET_FILE=`basename $TARGET_FILE` + done + + # Compute the canonicalized name by finding the physical path + # for the directory we're in and appending the target file. + PHYS_DIR=`pwd -P` + RESULT=$PHYS_DIR/$TARGET_FILE + echo $RESULT + } + rl="readlink" +else + rl="readlink -f" + if ! ${rl} "${0}" >/dev/null 2>&1; then + rl="realpath" + + if ! hash ${rl}; then + echo "\"${rl}\" not found !" && exit 1 + fi + fi +fi +cur=$(dirname "$(${rl} "${0}")") + +# dotdrop path can be pass as argument +ddpath="${cur}/../" +[ -n "${1}" ] && ddpath="${1}" +[ ! -d ${ddpath} ] && echo "ddpath \"${ddpath}\" is not a directory" && exit 1 + +export PYTHONPATH="${ddpath}:${PYTHONPATH}" +bin="python3 -m dotdrop.dotdrop" +hash coverage 2>/dev/null && bin="coverage run -a --source=dotdrop -m dotdrop.dotdrop" || true + +echo "dotdrop path: ${ddpath}" +echo "pythonpath: ${PYTHONPATH}" + +# get the helpers +source ${cur}/helpers + +echo -e "$(tput setaf 6)==> RUNNING $(basename $BASH_SOURCE) <==$(tput sgr0)" + +################################################################ +# this is the test +################################################################ + +# dotdrop directory +basedir=`mktemp -d --suffix='-dotdrop-tests' 2>/dev/null || mktemp -d` +echo "[+] dotdrop dir: ${basedir}" +echo "[+] dotpath dir: ${basedir}/dotfiles" +mkdir -p ${basedir}/dotfiles/a/{b,c} +echo 'a' > ${basedir}/dotfiles/a/b/abfile1 +echo 'a' > ${basedir}/dotfiles/a/b/abfile2 +echo 'a' > ${basedir}/dotfiles/a/b/abfile3 +echo 'a' > ${basedir}/dotfiles/a/c/acfile + +# the dotfile to be updated +tmpd=`mktemp -d --suffix='-dotdrop-tests' 2>/dev/null || mktemp -d` +cp -r ${basedir}/dotfiles/a ${tmpd}/ + +# create the config file +cfg="${basedir}/config.yaml" +cat > ${cfg} << _EOF +config: + backup: false + create: true + dotpath: dotfiles +dotfiles: + f_abc: + dst: ${tmpd}/a + src: a + upignore: + - "newdir/b/*" + - "!newdir/b/d" + - "b/abfile?" + - "!b/abfile3" +profiles: + p1: + dotfiles: + - f_abc +_EOF + +# edit/add files +echo "[+] edit/add files" +mkdir -p ${tmpd}/a/newdir/b +echo 'b' > ${tmpd}/a/b/abfile1 +echo 'b' > ${tmpd}/a/b/abfile2 +echo 'b' > ${tmpd}/a/b/abfile3 +echo 'b' > ${tmpd}/a/b/abfile4 +touch ${tmpd}/a/newdir/b/{c,d} + +# update +echo "[+] update" +cd ${ddpath} | ${bin} update -f -c ${cfg} --verbose --profile=p1 --key f_abc + +# check files haven't been updated +set +e +grep a ${basedir}/dotfiles/a/b/abfile1 >/dev/null 2>&1 || (echo "abfile1 should not have been updated" && exit 1) +grep a ${basedir}/dotfiles/a/b/abfile2 >/dev/null 2>&1 || (echo "abfile2 should not have been updated" && exit 1) +grep b ${basedir}/dotfiles/a/b/abfile3 >/dev/null 2>&1 || (echo "abfile3 should have been updated" && exit 1) +set -e +[ -e ${basedir}/dotfiles/a/b/abfile4 ] && echo "abfile4 should not have been updated" && exit 1 +[ -e ${basedir}/dotfiles/a/newdir/b/c ] && echo "newdir/b/c should not have been updated" && exit 1 +[ ! -e ${basedir}/dotfiles/a/newdir/b/d ] && echo "newdir/b/d should have been updated" && exit 1 + +## CLEANING +rm -rf ${basedir} ${tmpd} + +echo "OK" +exit 0 From 0c0acdd84378bd356d15aa6cd6c5da6d60eedf29 Mon Sep 17 00:00:00 2001 From: Joey Territo Date: Tue, 22 Dec 2020 21:56:00 -0500 Subject: [PATCH 16/19] Reformat updater.py and utils.py to match pycodestyle --- dotdrop/updater.py | 3 ++- dotdrop/utils.py | 7 ++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/dotdrop/updater.py b/dotdrop/updater.py index de38e21..c1b6ebd 100644 --- a/dotdrop/updater.py +++ b/dotdrop/updater.py @@ -281,7 +281,8 @@ class Updater: for ignore in self.ignores: for name in names: path = os.path.join(src, name) - if ignore.startswith('!') and fnmatch.fnmatch(path, ignore[1:]): + if ignore.startswith('!') and \ + fnmatch.fnmatch(path, ignore[1:]): # add to whitelist whitelist.add(name) elif fnmatch.fnmatch(path, ignore): diff --git a/dotdrop/utils.py b/dotdrop/utils.py index a601992..918f873 100644 --- a/dotdrop/utils.py +++ b/dotdrop/utils.py @@ -204,7 +204,8 @@ def must_ignore(paths, ignores, debug=False): return False if debug: LOG.dbg('must ignore? \"{}\" against {}'.format(paths, ignores)) - ignored_negative, ignored = categorize(lambda ign: ign.startswith('!'), ignores) + ignored_negative, ignored = categorize( + lambda ign: ign.startswith('!'), ignores) for p in paths: ignore_matches = [] # First ignore dotfiles @@ -387,5 +388,5 @@ def categorize(function, iterable): function(element) is true for each element and for which function(element) is false for each element""" - return tuple(filter(function, iterable)),\ - tuple(itertools.filterfalse(function, iterable)) + return (tuple(filter(function, iterable)), + tuple(itertools.filterfalse(function, iterable))) From ccd3133ebd4819c1dec8b1884b37c8e0b870e43a Mon Sep 17 00:00:00 2001 From: Joey Territo Date: Wed, 23 Dec 2020 20:52:32 -0500 Subject: [PATCH 17/19] Add warning for when negative ignore does not match an ignored file --- dotdrop/utils.py | 10 ++- tests-ng/negative-ignore-no-match.sh | 115 +++++++++++++++++++++++++++ 2 files changed, 123 insertions(+), 2 deletions(-) create mode 100755 tests-ng/negative-ignore-no-match.sh diff --git a/dotdrop/utils.py b/dotdrop/utils.py index 918f873..9189cda 100644 --- a/dotdrop/utils.py +++ b/dotdrop/utils.py @@ -221,8 +221,14 @@ def must_ignore(paths, ignores, debug=False): if fnmatch.fnmatch(p, ni): if debug: LOG.dbg('negative ignore \"{}\" match: {}'.format(ni, p)) - # TODO: catch error and report it - ignore_matches.remove(p) + try: + ignore_matches.remove(p) + except ValueError: + LOG.warn('no files that are currently being ignored match ' + '\"{}\". In order for a negative ignore pattern ' + 'to work, it must match a file that is being ' + 'ignored by a previous ignore pattern.'.format(ni) + ) if ignore_matches: return True if debug: diff --git a/tests-ng/negative-ignore-no-match.sh b/tests-ng/negative-ignore-no-match.sh new file mode 100755 index 0000000..74e3cd5 --- /dev/null +++ b/tests-ng/negative-ignore-no-match.sh @@ -0,0 +1,115 @@ +#!/usr/bin/env bash +# author: jtt9340 (https://github.com/jtt9340) +# +# test that dotdrop warns when a negative ignore pattern +# does not match a file that would be ignored +# returns 1 in case of error +# + +# exit on first error +set -e + +# all this crap to get current path +if [ $(uname) = Darwin ]; then + # Unfortunately, readlink works differently on macOS than it does on GNU/Linux + # (the -f option behaves differently) and the realpath command does not exist. + # Workarounds I find on the Internet suggest just using Homebrew to install coreutils + # so you can get the GNU coreutils on your Mac. But, I don't want this script to + # assume (a) users have Homebrew installed and (b) if they have Homebrew installed, that + # they then installed the GNU coreutils. + readlink() { + TARGET_FILE=$1 + + cd `dirname $TARGET_FILE` + TARGET_FILE=`basename $TARGET_FILE` + + # Iterate down a (possible) chain of symlinks + while [ -L "$TARGET_FILE" ]; do + TARGET_FILE=`readlink $TARGET_FILE` + cd `dirname $TARGET_FILE` + TARGET_FILE=`basename $TARGET_FILE` + done + + # Compute the canonicalized name by finding the physical path + # for the directory we're in and appending the target file. + PHYS_DIR=`pwd -P` + RESULT=$PHYS_DIR/$TARGET_FILE + echo $RESULT + } + rl="readlink" +else + rl="readlink -f" + if ! ${rl} "${0}" >/dev/null 2>&1; then + rl="realpath" + + if ! hash ${rl}; then + echo "\"${rl}\" not found !" && exit 1 + fi + fi +fi +cur=$(dirname "$(${rl} "${0}")") + +# dotdrop path can be pass as argument +ddpath="${cur}/../" +[ -n "${1}" ] && ddpath="${1}" +[ ! -d ${ddpath} ] && echo "ddpath \"${ddpath}\" is not a directory" && exit 1 + +export PYTHONPATH="${ddpath}:${PYTHONPATH}" +bin="python3 -m dotdrop.dotdrop" +hash coverage 2>/dev/null && bin="coverage run -a --source=dotdrop -m dotdrop.dotdrop" || true + +echo "dotdrop path: ${ddpath}" +echo "pythonpath: ${PYTHONPATH}" + +# get the helpers +source ${cur}/helpers + +echo -e "$(tput setaf 6)==> RUNNING $(basename $BASH_SOURCE) <==$(tput sgr0)" + +################################################################ +# this is the test +################################################################ + +# dotdrop directory +basedir=`mktemp -d --suffix='-dotdrop-tests' 2>/dev/null || mktemp -d` +echo "[+] dotdrop dir: ${basedir}" +echo "[+] dotpath dir: ${basedir}/dotfiles" + +# the dotfile to be imported +tmpd=`mktemp -d --suffix='-dotdrop-tests' 2>/dev/null || mktemp -d` + +# some files +mkdir -p ${tmpd}/program/ignore_me +echo "some data" > ${tmpd}/program/a +echo "some data" > ${tmpd}/program/ignore_me/b +echo "some data" > ${tmpd}/program/ignore_me/c + +# create the config file +cfg="${basedir}/config.yaml" +create_conf ${cfg} # sets token + +# import +echo "[+] import" +cd ${ddpath} | ${bin} import -f -c ${cfg} ${tmpd}/program + +# adding ignore in dotfile +cfg2="${basedir}/config2.yaml" +sed '/d_program:/a\ +\ \ \ \ instignore:\ +\ \ \ \ - "!*/ignore_me/c" +' ${cfg} > ${cfg2} + +# install +rm -rf ${tmpd} +echo "[+] install with negative ignore in dotfile" +echo '(1) expect dotdrop install to warn when negative ignore pattern does not match an already-ignored file' + +patt="[WARN] no files that are currently being ignored match \"*/ignore_me/c\". In order for a negative ignore pattern to work, it must match a file that is being ignored by a previous ignore pattern." +cd ${ddpath} | ${bin} install -c ${cfg2} --verbose 2>&1 >/dev/null | grep -F "${patt}" || + (echo "dotdrop did not warn when negative ignore pattern did not match an already-ignored file" && exit 1) + +## CLEANING +rm -rf ${basedir} ${tmpd} + +echo "OK" +exit 0 From 6d5955b0a5ba6c4308b94687cd52be4ec5240767 Mon Sep 17 00:00:00 2001 From: Joey Territo Date: Mon, 28 Dec 2020 21:59:42 -0500 Subject: [PATCH 18/19] Add documentation for the "negative ignore pattern" feature --- docs/config.md | 17 +++++++++++++++++ tests-ng/negative-ignore-no-match.sh | 3 ++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/docs/config.md b/docs/config.md index 5ae7634..0529c95 100644 --- a/docs/config.md +++ b/docs/config.md @@ -169,6 +169,12 @@ Make sure to quote those when using wildcards in the config file. Patterns used on a specific dotfile can be specified relative to the dotfile destination (`dst`). +Similar to a `.gitignore` file, you can prefix ignore patterns with an exclamation point (`!`). +This so-called "negative ignore pattern" will cause any files that match that pattern to __not__ be ignored, +provided they *would have* been ignored by an earlier ignore pattern (dotdrop will warn if that is not the +case). This feature allows you to, for example, ignore all files within a certain directory, except for one +particular one (see example below). + ```yaml config: cmpignore: @@ -216,3 +222,14 @@ config: - "testdir" ... ``` + +To ignore all files within a certain directory relative to `dst`, except one called `custom_plugin.zsh`: +```yaml +dotfiles: + d_zsh: + src: zsh + dst: ~/.config/zsh + impignore: + - "plugins/*" + - "!plugins/custom_plugin.zsh" +``` diff --git a/tests-ng/negative-ignore-no-match.sh b/tests-ng/negative-ignore-no-match.sh index 74e3cd5..7b2023a 100755 --- a/tests-ng/negative-ignore-no-match.sh +++ b/tests-ng/negative-ignore-no-match.sh @@ -104,7 +104,8 @@ rm -rf ${tmpd} echo "[+] install with negative ignore in dotfile" echo '(1) expect dotdrop install to warn when negative ignore pattern does not match an already-ignored file' -patt="[WARN] no files that are currently being ignored match \"*/ignore_me/c\". In order for a negative ignore pattern to work, it must match a file that is being ignored by a previous ignore pattern." +patt="[WARN] no files that are currently being ignored match \"*/ignore_me/c\". In order for a negative ignore +pattern to work, it must match a file that is being ignored by a previous ignore pattern." cd ${ddpath} | ${bin} install -c ${cfg2} --verbose 2>&1 >/dev/null | grep -F "${patt}" || (echo "dotdrop did not warn when negative ignore pattern did not match an already-ignored file" && exit 1) From a93d47556b5daf672f521900f051da4d6a1ae055 Mon Sep 17 00:00:00 2001 From: Joey Territo <54502648+jtt9340@users.noreply.github.com> Date: Tue, 29 Dec 2020 03:06:59 +0000 Subject: [PATCH 19/19] Fix formatting for YAML example in docs/config.md Accidentally used a hard tab instead of spaces when indenting a YAML code snippet --- docs/config.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/config.md b/docs/config.md index 0529c95..7ac4ff0 100644 --- a/docs/config.md +++ b/docs/config.md @@ -229,7 +229,7 @@ dotfiles: d_zsh: src: zsh dst: ~/.config/zsh - impignore: + impignore: - "plugins/*" - "!plugins/custom_plugin.zsh" ```