diff --git a/docs/config-format.md b/docs/config-format.md index 8494a2b..1e37a30 100644 --- a/docs/config-format.md +++ b/docs/config-format.md @@ -21,6 +21,7 @@ Entry | Description | Default `filter_file` | list of paths to load templating filters from (see [Templating available filters](templating.md#template-filters)) | - `func_file` | list of paths to load templating functions from (see [Templating available methods](templating.md#template-methods)) | - `ignoreempty` | do not deploy template if empty | false +`impignore` | list of patterns to ignore when importing (enclose in quotes when using wildcards, see [ignore patterns](config.md#ignore-patterns)) | - `import_actions` | list of paths to load actions from (absolute path or relative to the config file location, see [Import actions from file](config-details.md#entry-import_actions)) | - `import_configs` | list of config file paths to be imported in the current config (absolute path or relative to the current config file location, see [Import config files](config-details.md#entry-import_configs)) | - `import_variables` | list of paths to load variables from (absolute path or relative to the config file location see [Import variables from file](config-details.md#entry-import_variables)) | - diff --git a/docs/config.md b/docs/config.md index 973974f..5ae7634 100644 --- a/docs/config.md +++ b/docs/config.md @@ -151,11 +151,12 @@ profiles: ## Ignore patterns -It is possible to ignore specific patterns when using dotdrop. For example for `compare` when temporary -files don't need to appear in the output. +It is possible to ignore specific patterns when using dotdrop. * for [install](usage.md#install-dotfiles) * using `instignore` in the config file +* for [import](usage.md#import-dotfiles) + * using `impignore` in the config file * for [compare](usage.md#compare-dotfiles) * using `cmpignore` in the config file * using the command line switch `-i --ignore` @@ -207,3 +208,11 @@ dotfiles: - '*sub_directory_to_ignore' ``` +To ignore specific file `testfile` and directory `testdir` when importing: +```yaml +config: + impignore: + - "*/testfile" + - "testdir" +... +``` diff --git a/docs/usage.md b/docs/usage.md index 84de6bb..d550d75 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -68,6 +68,8 @@ dotfiles management. $ dotdrop import ~/.zshrc --as=~/.zshrc.test ``` +To ignore specific pattern during import see [the ignore patterns](config.md#ignore-patterns) + For more options, see the usage with `dotdrop --help` ## Install dotfiles diff --git a/dotdrop/dotdrop.py b/dotdrop/dotdrop.py index b2ed3b2..d6a89cd 100644 --- a/dotdrop/dotdrop.py +++ b/dotdrop/dotdrop.py @@ -467,7 +467,7 @@ def cmd_importer(o): paths = o.import_path importer = Importer(o.profile, o.conf, o.dotpath, o.diff_command, dry=o.dry, safe=o.safe, debug=o.debug, - keepdot=o.keepdot) + keepdot=o.keepdot, ignore=o.import_ignore) for path in paths: r = importer.import_path(path, import_as=o.import_as, diff --git a/dotdrop/importer.py b/dotdrop/importer.py index 5abaf3d..9f45fdf 100644 --- a/dotdrop/importer.py +++ b/dotdrop/importer.py @@ -11,7 +11,7 @@ import shutil # local imports from dotdrop.logger import Logger from dotdrop.utils import strip_home, get_default_file_perms, \ - get_file_perm, get_umask + get_file_perm, get_umask, must_ignore from dotdrop.linktypes import LinkTypes from dotdrop.comparator import Comparator @@ -20,7 +20,7 @@ class Importer: def __init__(self, profile, conf, dotpath, diff_cmd, dry=False, safe=True, debug=False, - keepdot=True): + keepdot=True, ignore=[]): """constructor @profile: the selected profile @conf: configuration manager @@ -30,6 +30,7 @@ class Importer: @safe: ask for overwrite if True @debug: enable debug @keepdot: keep dot prefix + @ignore: patterns to ignore when importing """ self.profile = profile self.conf = conf @@ -39,6 +40,7 @@ class Importer: self.safe = safe self.debug = debug self.keepdot = keepdot + self.ignore = ignore self.umask = get_umask() self.log = Logger() @@ -75,6 +77,10 @@ class Importer: dst = path.rstrip(os.sep) dst = os.path.abspath(dst) + # test if must be ignored + if self._ignore(dst): + return 0 + # ask confirmation for symlinks if self.safe: realdst = os.path.realpath(dst) @@ -141,6 +147,12 @@ class Importer: def _prepare_hierarchy(self, src, dst): """prepare hierarchy for dotfile""" srcf = os.path.join(self.dotpath, src) + if self._ignore(srcf): + return False + + srcfd = os.path.dirname(srcf) + if self._ignore(srcfd): + return False # a dotfile in dotpath already exists at that spot if os.path.exists(srcf): @@ -160,12 +172,12 @@ class Importer: self.log.dbg('will overwrite existing file') # create directory hierarchy - cmd = 'mkdir -p {}'.format(os.path.dirname(srcf)) if self.dry: + cmd = 'mkdir -p {}'.format(srcfd) self.log.dry('would run: {}'.format(cmd)) else: try: - os.makedirs(os.path.dirname(srcf), exist_ok=True) + os.makedirs(srcfd, exist_ok=True) except Exception: self.log.err('importing \"{}\" failed!'.format(dst)) return False @@ -177,12 +189,20 @@ class Importer: if os.path.isdir(dst): if os.path.exists(srcf): shutil.rmtree(srcf) - shutil.copytree(dst, srcf) + shutil.copytree(dst, srcf, copy_function=self._cp, + ignore=shutil.ignore_patterns(*self.ignore)) else: shutil.copy2(dst, srcf) return True + def _cp(self, src, dst): + """the copy function for copytree""" + # test if must be ignored + if self._ignore(src): + return + shutil.copy2(src, dst) + def _already_exists(self, src, dst): """ test no other dotfile exists with same @@ -201,3 +221,11 @@ class Importer: self.log.err('duplicate dotfile for this profile') return True return False + + def _ignore(self, path): + if must_ignore([path], self.ignore, debug=self.debug): + if self.debug: + self.log.dbg('ignoring import of {}'.format(path)) + self.log.warn('{} ignored'.format(path)) + return True + return False diff --git a/dotdrop/options.py b/dotdrop/options.py index 7fc7680..93edd7f 100644 --- a/dotdrop/options.py +++ b/dotdrop/options.py @@ -56,7 +56,7 @@ Usage: dotdrop install [-VbtfndDa] [-c ] [-p ] [-w ] [...] dotdrop import [-Vbdfm] [-c ] [-p ] [-s ] - [-l ] ... + [-l ] [-i ...] ... dotdrop compare [-LVb] [-c ] [-p ] [-w ] [-C ...] [-i ...] dotdrop update [-VbfdkP] [-c ] [-p ] @@ -269,6 +269,10 @@ class Options(AttrMonitor): self.import_path = self.args[''] self.import_as = self.args['--as'] self.import_mode = self.args['--preserve-mode'] + self.import_ignore = self.args['--ignore'] + self.import_ignore.extend(self.impignore) + self.import_ignore.append('*{}'.format(self.install_backup_suffix)) + self.import_ignore = uniq_list(self.import_ignore) # "update" specifics self.update_path = self.args[''] diff --git a/dotdrop/settings.py b/dotdrop/settings.py index f1062dc..faa28a3 100644 --- a/dotdrop/settings.py +++ b/dotdrop/settings.py @@ -32,6 +32,7 @@ class Settings(DictParser): key_link_on_import = 'link_on_import' key_showdiff = 'showdiff' key_upignore = 'upignore' + key_impignore = 'impignore' key_cmpignore = 'cmpignore' key_instignore = 'instignore' key_workdir = 'workdir' @@ -52,7 +53,7 @@ class Settings(DictParser): import_variables=[], keepdot=False, link_dotfile_default=LinkTypes.NOLINK, link_on_import=LinkTypes.NOLINK, longkey=False, - upignore=[], cmpignore=[], instignore=[], + upignore=[], cmpignore=[], instignore=[], impignore=[], workdir='~/.config/dotdrop', showdiff=False, minversion=None, func_file=[], filter_file=[], diff_command='diff -r -u {0} {1}', @@ -72,6 +73,7 @@ class Settings(DictParser): self.upignore = upignore self.cmpignore = cmpignore self.instignore = instignore + self.impignore = impignore self.workdir = workdir if ENV_WORKDIR in os.environ: self.workdir = os.environ[ENV_WORKDIR] @@ -113,6 +115,7 @@ class Settings(DictParser): self._serialize_seq(self.key_cmpignore, dic) self._serialize_seq(self.key_upignore, dic) self._serialize_seq(self.key_instignore, dic) + self._serialize_seq(self.key_impignore, dic) self._serialize_seq(self.key_func_file, dic) self._serialize_seq(self.key_filter_file, dic) diff --git a/dotdrop/utils.py b/dotdrop/utils.py index 6e2fdab..0b9f0c6 100644 --- a/dotdrop/utils.py +++ b/dotdrop/utils.py @@ -202,13 +202,15 @@ def must_ignore(paths, ignores, debug=False): if not ignores: return False if debug: - LOG.dbg('must ignore? {} against {}'.format(paths, ignores)) + LOG.dbg('must ignore? \"{}\" against {}'.format(paths, ignores)) for p in paths: for i in ignores: if fnmatch.fnmatch(p, i): if debug: LOG.dbg('ignore \"{}\" match: {}'.format(i, p)) return True + if debug: + LOG.dbg('NOT ignoring {}'.format(paths)) return False diff --git a/tests-ng/import-ignore.sh b/tests-ng/import-ignore.sh new file mode 100755 index 0000000..ed7c69b --- /dev/null +++ b/tests-ng/import-ignore.sh @@ -0,0 +1,102 @@ +#!/usr/bin/env bash +# author: deadc0de6 (https://github.com/deadc0de6) +# Copyright (c) 2020, deadc0de6 +# +# test ignore import +# 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" +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 +################################################################ + +# $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 +} + +# the dotfile source +tmps=`mktemp -d --suffix='-dotdrop-tests' || mktemp -d` +mkdir -p ${tmps}/dotfiles +# the dotfile destination +tmpd=`mktemp -d --suffix='-dotdrop-tests' || mktemp -d` +#echo "dotfile destination: ${tmpd}" + +# dotdrop directory +mkdir -p ${tmpd} +mkdir -p ${tmpd}/a/{b,c} +echo 'a' > ${tmpd}/a/b/abfile +echo 'a' > ${tmpd}/a/c/acfile +echo 'a' > ${tmpd}/a/b/newfile +mkdir -p ${tmpd}/a/newdir +echo 'a' > ${tmpd}/a/newdir/newfile + +# create the config file +cfg="${tmps}/config.yaml" +cat > ${cfg} << _EOF +config: + backup: false + create: true + dotpath: dotfiles + impignore: + - "*/cfile" + - "*/newfile" + - "newdir" +dotfiles: +profiles: +_EOF +#cat ${cfg} + +# import +echo "[+] import" +cd ${ddpath} | ${bin} import -c ${cfg} --verbose --profile=p1 ${tmpd}/a + +[ -d ${tmps}/dotfiles/newdir ] && echo "newdir not ignored" && exit 1 +[ -e ${tmps}/dotfiles/newdir/newfile ] && echo "newfile not ignored" && exit 1 +[ -e ${tmps}/dotfiles/a/b/newfile ] && echo "newfile not ignored" && exit 1 + +## CLEANING +rm -rf ${tmps} ${tmpd} + +echo "OK" +exit 0