diff --git a/dotdrop/comparator.py b/dotdrop/comparator.py index 992674d..eda8387 100644 --- a/dotdrop/comparator.py +++ b/dotdrop/comparator.py @@ -10,7 +10,7 @@ import filecmp # local imports from dotdrop.logger import Logger -import dotdrop.utils as utils +from dotdrop.utils import must_ignore, uniq_list, diff class Comparator: @@ -43,7 +43,7 @@ class Comparator: """compare a file""" if self.debug: self.log.dbg('compare file {} with {}'.format(left, right)) - if utils.must_ignore([left, right], ignore, debug=self.debug): + if must_ignore([left, right], ignore, debug=self.debug): if self.debug: self.log.dbg('ignoring diff {} and {}'.format(left, right)) return '' @@ -55,7 +55,7 @@ class Comparator: self.log.dbg('compare directory {} with {}'.format(left, right)) if not os.path.exists(right): return '' - if utils.must_ignore([left, right], ignore, debug=self.debug): + if must_ignore([left, right], ignore, debug=self.debug): if self.debug: self.log.dbg('ignoring diff {} and {}'.format(left, right)) return '' @@ -68,15 +68,15 @@ class Comparator: # handle files only in deployed dir for i in comp.left_only: - if utils.must_ignore([os.path.join(left, i)], - ignore, debug=self.debug): + if must_ignore([os.path.join(left, i)], + ignore, debug=self.debug): continue ret.append('=> \"{}\" does not exist on local\n'.format(i)) # handle files only in dotpath dir for i in comp.right_only: - if utils.must_ignore([os.path.join(right, i)], - ignore, debug=self.debug): + if must_ignore([os.path.join(right, i)], + ignore, debug=self.debug): continue ret.append('=> \"{}\" does not exist in dotdrop\n'.format(i)) @@ -85,8 +85,8 @@ class Comparator: for i in funny: lfile = os.path.join(left, i) rfile = os.path.join(right, i) - if utils.must_ignore([lfile, rfile], - ignore, debug=self.debug): + if must_ignore([lfile, rfile], + ignore, debug=self.debug): continue short = os.path.basename(lfile) # file vs dir @@ -95,12 +95,12 @@ class Comparator: # content is different funny = comp.diff_files funny.extend(comp.funny_files) - funny = utils.uniq_list(funny) + funny = uniq_list(funny) for i in funny: lfile = os.path.join(left, i) rfile = os.path.join(right, i) - if utils.must_ignore([lfile, rfile], - ignore, debug=self.debug): + if must_ignore([lfile, rfile], + ignore, debug=self.debug): continue diff = self._diff(lfile, rfile, header=True) ret.append(diff) @@ -115,9 +115,9 @@ class Comparator: def _diff(self, left, right, header=False): """diff using the unix tool diff""" - diff = utils.diff(left, right, raw=False, - opts=self.diffopts, debug=self.debug) + d = diff(left, right, raw=False, + opts=self.diffopts, debug=self.debug) if header: lshort = os.path.basename(left) - diff = '=> diff \"{}\":\n{}'.format(lshort, diff) - return diff + d = '=> diff \"{}\":\n{}'.format(lshort, diff) + return d diff --git a/dotdrop/dotdrop.py b/dotdrop/dotdrop.py index 7f49186..5e575d5 100644 --- a/dotdrop/dotdrop.py +++ b/dotdrop/dotdrop.py @@ -15,7 +15,8 @@ from dotdrop.templategen import Templategen from dotdrop.installer import Installer from dotdrop.updater import Updater from dotdrop.comparator import Comparator -from dotdrop.utils import get_tmpdir, remove, strip_home, run, uniq_list +from dotdrop.utils import get_tmpdir, remove, strip_home, \ + run, uniq_list, patch_ignores from dotdrop.linktypes import LinkTypes from dotdrop.exceptions import YamlException @@ -225,6 +226,7 @@ def cmd_compare(o, tmp): same = False continue ignores = list(set(o.compare_ignore + dotfile.cmpignore)) + ignores = patch_ignores(ignores, dotfile.src) diff = comp.compare(insttmp, dotfile.dst, ignore=ignores) if tmpsrc: # clean tmp transformed dotfile if any diff --git a/dotdrop/updater.py b/dotdrop/updater.py index 1ee2148..48df214 100644 --- a/dotdrop/updater.py +++ b/dotdrop/updater.py @@ -12,7 +12,8 @@ import filecmp # local imports from dotdrop.logger import Logger from dotdrop.templategen import Templategen -import dotdrop.utils as utils +from dotdrop.utils import patch_ignores, remove, get_unique_tmp_name, \ + write_to_tmpfile, must_ignore TILD = '~' @@ -76,7 +77,8 @@ class Updater: """update dotfile from file pointed by path""" ret = False new_path = None - self.ignores = list(set(self.ignore + dotfile.upignore)) + ignores = list(set(self.ignore + dotfile.upignore)) + self.ignores = patch_ignores(ignores, dotfile.dst) if self.debug: self.log.dbg('ignore pattern(s): {}'.format(self.ignores)) @@ -98,7 +100,7 @@ class Updater: ret = self._handle_file(path, dtpath) # clean temporary files if new_path and os.path.exists(new_path): - utils.remove(new_path) + remove(new_path) return ret def _apply_trans_w(self, path, dotfile): @@ -108,12 +110,12 @@ class Updater: return path if self.debug: self.log.dbg('executing write transformation {}'.format(trans)) - tmp = utils.get_unique_tmp_name() + tmp = get_unique_tmp_name() if not trans.transform(path, tmp): msg = 'transformation \"{}\" failed for {}' self.log.err(msg.format(trans.key, dotfile.key)) if os.path.exists(tmp): - utils.remove(tmp) + remove(tmp) return None return tmp @@ -128,7 +130,7 @@ class Updater: def _show_patch(self, fpath, tpath): """provide a way to manually patch the template""" content = self._resolve_template(tpath) - tmp = utils.write_to_tmpfile(content) + tmp = write_to_tmpfile(content) cmds = ['diff', '-u', tmp, fpath, '|', 'patch', tpath] self.log.warn('try patching with: \"{}\"'.format(' '.join(cmds))) return False @@ -231,7 +233,7 @@ class Updater: self.log.dbg('rm -r {}'.format(old)) if not self._confirm_rm_r(old): continue - utils.remove(old) + remove(old) self.log.sub('\"{}\" dir removed'.format(old)) # handle files diff @@ -283,7 +285,7 @@ class Updater: continue if self.debug: self.log.dbg('rm {}'.format(new)) - utils.remove(new) + remove(new) self.log.sub('\"{}\" removed'.format(new)) # Recursively decent into common subdirectories. @@ -308,7 +310,7 @@ class Updater: return True def _ignore(self, paths): - if utils.must_ignore(paths, self.ignores, debug=self.debug): + if must_ignore(paths, self.ignores, debug=self.debug): if self.debug: self.log.dbg('ignoring update for {}'.format(paths)) return True diff --git a/dotdrop/utils.py b/dotdrop/utils.py index 2954a96..ac36a74 100644 --- a/dotdrop/utils.py +++ b/dotdrop/utils.py @@ -17,6 +17,7 @@ from shutil import rmtree from dotdrop.logger import Logger LOG = Logger() +STAR = '*' def run(cmd, raw=True, debug=False, checkerr=False): @@ -139,3 +140,21 @@ def uniq_list(a_list): if a not in new: new.append(a) return new + + +def patch_ignores(ignores, prefix): + """allow relative ignore pattern""" + new = [] + for ignore in ignores: + if STAR in ignore: + # is glob + new.append(ignore) + continue + if os.path.isabs(ignore): + # is absolute + new.append(ignore) + continue + # patch upignore + path = os.path.join(prefix, ignore) + new.append(path) + return new diff --git a/tests-ng/compare-ignore-relative.sh b/tests-ng/compare-ignore-relative.sh new file mode 100755 index 0000000..519d8bf --- /dev/null +++ b/tests-ng/compare-ignore-relative.sh @@ -0,0 +1,159 @@ +#!/usr/bin/env bash +# author: deadc0de6 (https://github.com/deadc0de6) +# Copyright (c) 2019, deadc0de6 +# +# test compare ignore relative +# returns 1 in case of error +# + +# exit on first error +#set -e + +# all this crap to get current path +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 +cur=$(dirname "$(${rl} "${0}")") + +#hash dotdrop >/dev/null 2>&1 +#[ "$?" != "0" ] && echo "install dotdrop to run tests" && exit 1 + +#echo "called with ${1}" + +# 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 "\e[96m\e[1m==> RUNNING $(basename $BASH_SOURCE) <==\e[0m" + +################################################################ +# this is the test +################################################################ + +# dotdrop directory +basedir=`mktemp -d --suffix='-dotdrop-tests'` +echo "[+] dotdrop dir: ${basedir}" +echo "[+] dotpath dir: ${basedir}/dotfiles" + +# the dotfile to be imported +tmpd=`mktemp -d --suffix='-dotdrop-tests'` + +# some files +mkdir -p ${tmpd}/{program,config} +touch ${tmpd}/program/a +touch ${tmpd}/config/a +mkdir ${tmpd}/vscode +touch ${tmpd}/vscode/extensions.txt +touch ${tmpd}/vscode/keybindings.json + +# create the config file +cfg="${basedir}/config.yaml" +create_conf ${cfg} # sets token + +# import +echo "[+] import" +cd ${ddpath} | ${bin} import -c ${cfg} ${tmpd}/program +cd ${ddpath} | ${bin} import -c ${cfg} ${tmpd}/config +cd ${ddpath} | ${bin} import -c ${cfg} ${tmpd}/vscode + +# add files +echo "[+] add files" +touch ${tmpd}/program/b +touch ${tmpd}/config/b + +# expects diff +echo "[+] comparing normal - 2 diffs" +set +e +cd ${ddpath} | ${bin} compare -c ${cfg} --verbose +[ "$?" = "0" ] && exit 1 +set -e + +# expects one diff +patt="${tmpd}/config/b" +echo "[+] comparing with ignore (pattern: ${patt}) - 1 diff" +set +e +cd ${ddpath} | ${bin} compare -c ${cfg} --verbose --ignore=${patt} +[ "$?" = "0" ] && exit 1 +set -e + +# expects no diff +patt="*b" +echo "[+] comparing with ignore (pattern: ${patt}) - 0 diff" +set +e +cd ${ddpath} | ${bin} compare -c ${cfg} --verbose --ignore=${patt} +[ "$?" != "0" ] && exit 1 +set -e + +# expects one diff +patt="*/config/*b" +echo "[+] comparing with ignore (pattern: ${patt}) - 1 diff" +set +e +cd ${ddpath} | ${bin} compare -c ${cfg} --verbose --ignore=${patt} +[ "$?" = "0" ] && exit 1 +set -e + +#cat ${cfg} + +# adding ignore in dotfile +cfg2="${basedir}/config2.yaml" +sed '/d_config:/a \ \ \ \ cmpignore:\n\ \ \ \ - "config/b"' ${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 + +# adding ignore in dotfile +cfg2="${basedir}/config2.yaml" +sed '/d_config:/a \ \ \ \ cmpignore:\n\ \ \ \ - "b"' ${cfg} > ${cfg2} +sed -i '/d_program:/a \ \ \ \ cmpignore:\n\ \ \ \ - "b"' ${cfg2} +#cat ${cfg2} + +# expects no diff +patt="*b" +echo "[+] comparing with ignore in dotfile - 0 diff" +set +e +cd ${ddpath} | ${bin} compare -c ${cfg2} --verbose +[ "$?" != "0" ] && exit 1 +set -e + +# update files +echo touched > ${tmpd}/vscode/extensions.txt +echo touched > ${tmpd}/vscode/keybindings.json + +# expect two diffs +set +e +cd ${ddpath} | ${bin} compare -c ${cfg} --verbose -C ${tmpd}/vscode +[ "$?" = "0" ] && exit 1 +set -e + +# expects no diff +sed '/d_vscode:/a \ \ \ \ cmpignore:\n\ \ \ \ - "extensions.txt"\n\ \ \ \ - "keybindings.json"' ${cfg} > ${cfg2} +set +e +cd ${ddpath} | ${bin} compare -c ${cfg2} --verbose -C ${tmpd}/vscode +[ "$?" != "0" ] && exit 1 +set -e + +## CLEANING +rm -rf ${basedir} ${tmpd} + +echo "OK" +exit 0 diff --git a/tests-ng/update-ignore-relative.sh b/tests-ng/update-ignore-relative.sh new file mode 100755 index 0000000..1dc36f1 --- /dev/null +++ b/tests-ng/update-ignore-relative.sh @@ -0,0 +1,107 @@ +#!/usr/bin/env bash +# author: deadc0de6 (https://github.com/deadc0de6) +# Copyright (c) 2019, deadc0de6 +# +# test ignore update relative pattern +# returns 1 in case of error +# + +# exit on first error +#set -e + +# all this crap to get current path +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 +cur=$(dirname "$(${rl} "${0}")") + +#hash dotdrop >/dev/null 2>&1 +#[ "$?" != "0" ] && echo "install dotdrop to run tests" && exit 1 + +#echo "called with ${1}" + +# 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 "\e[96m\e[1m==> RUNNING $(basename $BASH_SOURCE) <==\e[0m" + +################################################################ +# this is the test +################################################################ + +# dotdrop directory +tmps=`mktemp -d --suffix='-dotdrop-tests'` +dt="${tmps}/dotfiles" +mkdir -p ${dt} +mkdir -p ${dt}/a/{b,c} +echo 'a' > ${dt}/a/b/abfile +echo 'a' > ${dt}/a/c/acfile + +# fs dotfiles +tmpd=`mktemp -d --suffix='-dotdrop-tests'` +cp -r ${dt}/a ${tmpd}/ + +# create the config file +cfg="${tmps}/config.yaml" +cat > ${cfg} << _EOF +config: + backup: false + create: true + dotpath: dotfiles +dotfiles: + f_abc: + dst: ${tmpd}/a + src: a + upignore: + - "cfile" + - "newfile" + - "newdir" +profiles: + p1: + dotfiles: + - f_abc +_EOF +#cat ${cfg} + +#tree ${dt} + +# edit/add files +echo "[+] edit/add files" +touch ${tmpd}/a/newfile +echo 'b' > ${tmpd}/a/c/acfile +mkdir -p ${tmpd}/a/newdir/b +touch ${tmpd}/a/newdir/b/c + +#tree ${tmpd}/a + +# update +echo "[+] update" +cd ${ddpath} | ${bin} update -f -c ${cfg} --verbose --profile=p1 --key f_abc + +#tree ${dt} + +# check files haven't been updated +grep 'b' ${dt}/a/c/acfile >/dev/null +[ -e ${dt}/a/newfile ] && exit 1 + +## CLEANING +rm -rf ${tmps} ${tmpd} + +echo "OK" +exit 0