1
0
mirror of https://github.com/deadc0de6/dotdrop.git synced 2026-02-16 21:24:10 +00:00

implement upignore for #79

This commit is contained in:
deadc0de6
2019-01-17 21:22:12 +01:00
parent f78e3126f5
commit 0162412d4c
8 changed files with 199 additions and 28 deletions

View File

@@ -212,7 +212,7 @@ that don't need to appear in the output of compare.
Either use the command line switch `-i --ignore` or add an entry in the dotfile Either use the command line switch `-i --ignore` or add an entry in the dotfile
directly in the `cmpignore` entry (see [Config](#config)). directly in the `cmpignore` entry (see [Config](#config)).
The pattern follows Unix shell-style wildcards like for example `*/path/file`. The ignore pattern must follow Unix shell-style wildcards like for example `*/path/file`.
Make sure to quote those when using wildcards in the config file. Make sure to quote those when using wildcards in the config file.
It is also possible to install all dotfiles for a specific profile It is also possible to install all dotfiles for a specific profile
@@ -474,6 +474,21 @@ $ dotdrop update ~/.vimrc
$ dotdrop update --key f_vimrc $ dotdrop update --key f_vimrc
``` ```
It is possible to ignore files to update using unix pattern by providing those
either through the switch `-i --ignore` or as part of the dotfile under the
key `upignore` (see [Config](#config)).
The ignore pattern must follow Unix shell-style wildcards like for example `*/path/file`.
Make sure to quote those when using wildcards in the config file.
```yaml
dotfiles:
d_vim
dst: ~/.vim
src: vim
upignore:
- "*/undo-dir"
- "*/plugged"
```
There are two cases when updating a dotfile: There are two cases when updating a dotfile:
**The dotfile doesn't use [templating](#template)** **The dotfile doesn't use [templating](#template)**
@@ -564,6 +579,7 @@ the following entries:
* `src`: dotfile path within the `dotpath` (can use `variables` and `dynvariables`, make sure to quote). * `src`: dotfile path within the `dotpath` (can use `variables` and `dynvariables`, make sure to quote).
* `link`: if true dotdrop will create a symlink instead of copying (default *false*). * `link`: if true dotdrop will create a symlink instead of copying (default *false*).
* `cmpignore`: list of pattern to ignore when comparing (enclose in quotes when using wildcards). * `cmpignore`: list of pattern to ignore when comparing (enclose in quotes when using wildcards).
* `upignore`: list of pattern to ignore when updating (enclose in quotes when using wildcards).
* `actions`: list of action keys that need to be defined in the **actions** entry below. * `actions`: list of action keys that need to be defined in the **actions** entry below.
* `trans`: transformation key to apply when installing this dotfile (must be defined in the **trans** entry below). * `trans`: transformation key to apply when installing this dotfile (must be defined in the **trans** entry below).
* `trans_write`: transformation key to apply when updating this dotfile (must be defined in the **trans_write** entry below). * `trans_write`: transformation key to apply when updating this dotfile (must be defined in the **trans_write** entry below).
@@ -578,6 +594,8 @@ the following entries:
ignoreempty: <true|false> ignoreempty: <true|false>
cmpignore: cmpignore:
- "<ignore-pattern>" - "<ignore-pattern>"
upignore:
- "<ignore-pattern>"
actions: actions:
- <action-key> - <action-key>
trans: <transformation-key> trans: <transformation-key>

View File

@@ -7,7 +7,6 @@ handle the comparison of dotfiles and local deployment
import os import os
import filecmp import filecmp
import fnmatch
# local imports # local imports
from dotdrop.logger import Logger from dotdrop.logger import Logger
@@ -34,7 +33,7 @@ class Comparator:
def _comp_file(self, left, right, ignore): def _comp_file(self, left, right, ignore):
"""compare a file""" """compare a file"""
if self._ignore([left, right], ignore): if utils.must_ignore([left, right], ignore, debug=self.debug):
if self.debug: if self.debug:
self.log.dbg('ignoring diff {} and {}'.format(left, right)) self.log.dbg('ignoring diff {} and {}'.format(left, right))
return '' return ''
@@ -44,7 +43,7 @@ class Comparator:
"""compare a directory""" """compare a directory"""
if not os.path.exists(right): if not os.path.exists(right):
return '' return ''
if self._ignore([left, right], ignore): if utils.must_ignore([left, right], ignore, debug=self.debug):
if self.debug: if self.debug:
self.log.dbg('ignoring diff {} and {}'.format(left, right)) self.log.dbg('ignoring diff {} and {}'.format(left, right))
return '' return ''
@@ -54,11 +53,13 @@ class Comparator:
comp = filecmp.dircmp(left, right) comp = filecmp.dircmp(left, right)
# handle files only in deployed file # handle files only in deployed file
for i in comp.left_only: for i in comp.left_only:
if self._ignore([os.path.join(left, i)], ignore): if utils.must_ignore([os.path.join(left, i)],
ignore, debug=self.debug):
continue continue
ret.append('=> \"{}\" does not exist on local\n'.format(i)) ret.append('=> \"{}\" does not exist on local\n'.format(i))
for i in comp.right_only: for i in comp.right_only:
if self._ignore([os.path.join(right, i)], ignore): if utils.must_ignore([os.path.join(right, i)],
ignore, debug=self.debug):
continue continue
ret.append('=> \"{}\" does not exist in dotdrop\n'.format(i)) ret.append('=> \"{}\" does not exist in dotdrop\n'.format(i))
@@ -92,15 +93,3 @@ class Comparator:
rshort = os.path.basename(right) rshort = os.path.basename(right)
diff = '=> diff \"{}\":\n{}'.format(lshort, diff) diff = '=> diff \"{}\":\n{}'.format(lshort, diff)
return diff return diff
def _ignore(self, paths, ignore):
'''return True if any paths is ignored - not very efficient'''
if not ignore:
return False
for p in paths:
for i in ignore:
if fnmatch.fnmatch(p, i):
if self.debug:
self.log.dbg('ignore match {}'.format(p))
return True
return False

View File

@@ -57,6 +57,7 @@ class Cfg:
key_dotfiles_actions = 'actions' key_dotfiles_actions = 'actions'
key_dotfiles_trans_r = 'trans' key_dotfiles_trans_r = 'trans'
key_dotfiles_trans_w = 'trans_write' key_dotfiles_trans_w = 'trans_write'
key_dotfiles_upignore = 'upignore'
# profiles keys # profiles keys
key_profiles = 'profiles' key_profiles = 'profiles'
@@ -272,15 +273,20 @@ class Cfg:
trans_r = None trans_r = None
trans_w = None trans_w = None
# parse ignore pattern # parse cmpignore pattern
ignores = v[self.key_dotfiles_cmpignore] if \ cmpignores = v[self.key_dotfiles_cmpignore] if \
self.key_dotfiles_cmpignore in v else [] self.key_dotfiles_cmpignore in v else []
# parse upignore pattern
upignores = v[self.key_dotfiles_upignore] if \
self.key_dotfiles_upignore in v else []
# create new dotfile # create new dotfile
self.dotfiles[k] = Dotfile(k, dst, src, self.dotfiles[k] = Dotfile(k, dst, src,
link=link, actions=actions, link=link, actions=actions,
trans_r=trans_r, trans_w=trans_w, trans_r=trans_r, trans_w=trans_w,
cmpignore=ignores, noempty=noempty) cmpignore=cmpignores, noempty=noempty,
upignore=upignores)
# assign dotfiles to each profile # assign dotfiles to each profile
for k, v in self.lnk_profiles.items(): for k, v in self.lnk_profiles.items():

View File

@@ -44,7 +44,8 @@ Usage:
dotdrop import [-ldVb] [-c <path>] [-p <profile>] <path>... dotdrop import [-ldVb] [-c <path>] [-p <profile>] <path>...
dotdrop compare [-Vb] [-c <path>] [-p <profile>] dotdrop compare [-Vb] [-c <path>] [-p <profile>]
[-o <opts>] [-C <file>...] [-i <pattern>...] [-o <opts>] [-C <file>...] [-i <pattern>...]
dotdrop update [-fdVbk] [-c <path>] [-p <profile>] [<path>...] dotdrop update [-fdVbk] [-c <path>] [-p <profile>]
[-i <pattern>...] [<path>...]
dotdrop listfiles [-VTb] [-c <path>] [-p <profile>] dotdrop listfiles [-VTb] [-c <path>] [-p <profile>]
dotdrop detail [-Vb] [-c <path>] [-p <profile>] [<key>...] dotdrop detail [-Vb] [-c <path>] [-p <profile>] [<key>...]
dotdrop list [-Vb] [-c <path>] dotdrop list [-Vb] [-c <path>]
@@ -55,7 +56,7 @@ Options:
-p --profile=<profile> Specify the profile to use [default: {}]. -p --profile=<profile> Specify the profile to use [default: {}].
-c --cfg=<path> Path to the config [default: config.yaml]. -c --cfg=<path> Path to the config [default: config.yaml].
-C --file=<path> Path of dotfile to compare. -C --file=<path> Path of dotfile to compare.
-i --ignore=<pattern> Pattern to ignore when diffing. -i --ignore=<pattern> Pattern to ignore.
-o --dopts=<opts> Diff options [default: ]. -o --dopts=<opts> Diff options [default: ].
-n --nodiff Do not diff when installing. -n --nodiff Do not diff when installing.
-t --temp Install to a temporary directory for review. -t --temp Install to a temporary directory for review.
@@ -209,11 +210,12 @@ def cmd_compare(opts, conf, tmp, focus=[], ignore=[]):
return same return same
def cmd_update(opts, conf, paths, iskey=False): def cmd_update(opts, conf, paths, iskey=False, ignore=[]):
"""update the dotfile(s) from path(s) or key(s)""" """update the dotfile(s) from path(s) or key(s)"""
ret = True ret = True
updater = Updater(conf, opts['dotpath'], opts['dry'], updater = Updater(conf, opts['dotpath'], opts['dry'],
opts['safe'], iskey=iskey, debug=opts['debug']) opts['safe'], iskey=iskey,
debug=opts['debug'], ignore=[])
if not iskey: if not iskey:
# update paths # update paths
if opts['debug']: if opts['debug']:
@@ -494,7 +496,8 @@ def main():
if opts['debug']: if opts['debug']:
LOG.dbg('running cmd: update') LOG.dbg('running cmd: update')
iskey = args['--key'] iskey = args['--key']
ret = cmd_update(opts, conf, args['<path>'], iskey=iskey) ret = cmd_update(opts, conf, args['<path>'], iskey=iskey,
ignore=args['--ignore'])
elif args['detail']: elif args['detail']:
# detail files # detail files

View File

@@ -10,7 +10,8 @@ class Dotfile:
def __init__(self, key, dst, src, def __init__(self, key, dst, src,
actions={}, trans_r=None, trans_w=None, actions={}, trans_r=None, trans_w=None,
link=False, cmpignore=[], noempty=False): link=False, cmpignore=[], noempty=False,
upignore=[]):
# key of dotfile in the config # key of dotfile in the config
self.key = key self.key = key
# path where to install this dotfile # path where to install this dotfile
@@ -29,6 +30,8 @@ class Dotfile:
self.cmpignore = cmpignore self.cmpignore = cmpignore
# do not deploy empty file # do not deploy empty file
self.noempty = noempty self.noempty = noempty
# pattern to ignore when updating
self.upignore = upignore
def __str__(self): def __str__(self):
msg = 'key:\"{}\", src:\"{}\", dst:\"{}\", link:\"{}\"' msg = 'key:\"{}\", src:\"{}\", dst:\"{}\", link:\"{}\"'

View File

@@ -21,13 +21,14 @@ TILD = '~'
class Updater: class Updater:
def __init__(self, conf, dotpath, dry, safe, def __init__(self, conf, dotpath, dry, safe,
iskey=False, debug=False): iskey=False, debug=False, ignore=[]):
self.conf = conf self.conf = conf
self.dotpath = dotpath self.dotpath = dotpath
self.dry = dry self.dry = dry
self.safe = safe self.safe = safe
self.iskey = iskey self.iskey = iskey
self.debug = debug self.debug = debug
self.ignore = ignore
self.log = Logger() self.log = Logger()
def update_path(self, path, profile): def update_path(self, path, profile):
@@ -58,9 +59,16 @@ class Updater:
"""update dotfile from file pointed by path""" """update dotfile from file pointed by path"""
ret = False ret = False
new_path = None new_path = None
self.ignores = list(set(self.ignore + dotfile.upignore))
if self.debug:
self.log.dbg('ignore pattern(s): {}'.format(self.ignores))
left = os.path.expanduser(path) left = os.path.expanduser(path)
right = os.path.join(self.conf.abs_or_rel(self.dotpath), dotfile.src) right = os.path.join(self.conf.abs_or_rel(self.dotpath), dotfile.src)
right = os.path.expanduser(right) right = os.path.expanduser(right)
if self._ignore([left, right]):
return True
if dotfile.trans_w: if dotfile.trans_w:
# apply write transformation if any # apply write transformation if any
new_path = self._apply_trans_w(path, dotfile) new_path = self._apply_trans_w(path, dotfile)
@@ -137,6 +145,8 @@ class Updater:
def _handle_file(self, left, right, compare=True): def _handle_file(self, left, right, compare=True):
"""sync left (deployed file) and right (dotdrop dotfile)""" """sync left (deployed file) and right (dotdrop dotfile)"""
if self._ignore([left, right]):
return True
if self.debug: if self.debug:
self.log.dbg('update for file {} and {}'.format(left, right)) self.log.dbg('update for file {} and {}'.format(left, right))
if self._is_template(right): if self._is_template(right):
@@ -169,6 +179,8 @@ class Updater:
# paths must be absolute (no tildes) # paths must be absolute (no tildes)
left = os.path.expanduser(left) left = os.path.expanduser(left)
right = os.path.expanduser(right) right = os.path.expanduser(right)
if self._ignore([left, right]):
return True
# find the differences # find the differences
diff = filecmp.dircmp(left, right, ignore=None) diff = filecmp.dircmp(left, right, ignore=None)
# handle directories diff # handle directories diff
@@ -179,6 +191,8 @@ class Updater:
left, right = diff.left, diff.right left, right = diff.left, diff.right
if self.debug: if self.debug:
self.log.dbg('sync dir {} to {}'.format(left, right)) self.log.dbg('sync dir {} to {}'.format(left, right))
if self._ignore([left, right]):
return True
# create dirs that don't exist in dotdrop # create dirs that don't exist in dotdrop
for toadd in diff.left_only: for toadd in diff.left_only:
@@ -188,6 +202,8 @@ class Updater:
continue continue
# match to dotdrop dotpath # match to dotdrop dotpath
new = os.path.join(right, toadd) new = os.path.join(right, toadd)
if self._ignore([exist, new]):
continue
if self.dry: if self.dry:
self.log.dry('would cp -r {} {}'.format(exist, new)) self.log.dry('would cp -r {} {}'.format(exist, new))
continue continue
@@ -202,6 +218,8 @@ class Updater:
if not os.path.isdir(old): if not os.path.isdir(old):
# ignore files for now # ignore files for now
continue continue
if self._ignore([old]):
continue
if self.dry: if self.dry:
self.log.dry('would rm -r {}'.format(old)) self.log.dry('would rm -r {}'.format(old))
continue continue
@@ -219,6 +237,8 @@ class Updater:
for f in fdiff: for f in fdiff:
fleft = os.path.join(left, f) fleft = os.path.join(left, f)
fright = os.path.join(right, f) fright = os.path.join(right, f)
if self._ignore([fleft, fright]):
continue
if self.dry: if self.dry:
self.log.dry('would cp {} {}'.format(fleft, fright)) self.log.dry('would cp {} {}'.format(fleft, fright))
continue continue
@@ -233,6 +253,8 @@ class Updater:
# ignore dirs, done above # ignore dirs, done above
continue continue
new = os.path.join(right, toadd) new = os.path.join(right, toadd)
if self._ignore([exist, new]):
continue
if self.dry: if self.dry:
self.log.dry('would cp {} {}'.format(exist, new)) self.log.dry('would cp {} {}'.format(exist, new))
continue continue
@@ -248,6 +270,8 @@ class Updater:
if os.path.isdir(new): if os.path.isdir(new):
# ignore dirs, done above # ignore dirs, done above
continue continue
if self._ignore([new]):
continue
if self.dry: if self.dry:
self.log.dry('would rm {}'.format(new)) self.log.dry('would rm {}'.format(new))
continue continue
@@ -275,3 +299,10 @@ class Updater:
if self.safe and not self.log.ask(msg): if self.safe and not self.log.ask(msg):
return False return False
return True return True
def _ignore(self, paths):
if utils.must_ignore(paths, self.ignores, debug=self.debug):
if self.debug:
self.log.dbg('ignoring update for {}'.format(paths))
return True
return False

View File

@@ -10,6 +10,7 @@ import tempfile
import os import os
import uuid import uuid
import shlex import shlex
import fnmatch
from shutil import rmtree from shutil import rmtree
# local import # local import
@@ -109,3 +110,16 @@ def strip_home(path):
if path.startswith(home): if path.startswith(home):
path = path[len(home):] path = path[len(home):]
return path return path
def must_ignore(paths, ignores, debug=False):
"""return true if any paths in list matches any ignore patterns"""
if not ignores:
return False
for p in paths:
for i in ignores:
if fnmatch.fnmatch(p, i):
if debug:
LOG.dbg('ignore \"{}\" match: {}'.format(i, p))
return True
return False

107
tests-ng/update-ignore.sh Executable file
View File

@@ -0,0 +1,107 @@
#!/usr/bin/env bash
# author: deadc0de6 (https://github.com/deadc0de6)
# Copyright (c) 2017, deadc0de6
#
# test ignore update
# 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`
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`
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