From 8081b4adf54fff3566e3ff174bc56cf2f5aa35f6 Mon Sep 17 00:00:00 2001 From: Marcel Robitaille Date: Fri, 11 Dec 2020 00:54:51 -0500 Subject: [PATCH 01/15] Option to ignore missing files update and compare --- dotdrop/comparator.py | 7 +++++-- dotdrop/dotdrop.py | 6 ++++-- dotdrop/options.py | 8 +++++--- dotdrop/updater.py | 9 +++++++-- dotdrop/utils.py | 6 +++++- tests/helpers.py | 1 + 6 files changed, 27 insertions(+), 10 deletions(-) diff --git a/dotdrop/comparator.py b/dotdrop/comparator.py index 634a489..6159afb 100644 --- a/dotdrop/comparator.py +++ b/dotdrop/comparator.py @@ -16,7 +16,7 @@ from dotdrop.utils import must_ignore, uniq_list, diff, \ class Comparator: - def __init__(self, diff_cmd='', debug=False): + def __init__(self, diff_cmd='', debug=False, ignore_missing_in_dotdrop=False): """constructor @diff_cmd: diff command to use @debug: enable debug @@ -24,6 +24,7 @@ class Comparator: self.diff_cmd = diff_cmd self.debug = debug self.log = Logger() + self.ignore_missing_in_dotdrop = ignore_missing_in_dotdrop def compare(self, left, right, ignore=[]): """diff left (dotdrop dotfile) and right (deployed file)""" @@ -111,7 +112,9 @@ class Comparator: if must_ignore([os.path.join(right, i)], ignore, debug=self.debug): continue - ret.append('=> \"{}\" does not exist in dotdrop\n'.format(i)) + + if not self.ignore_missing_in_dotdrop: + ret.append('=> \"{}\" does not exist in dotdrop\n'.format(i)) # same left and right but different type funny = comp.common_funny diff --git a/dotdrop/dotdrop.py b/dotdrop/dotdrop.py index 47b9679..49c79e0 100644 --- a/dotdrop/dotdrop.py +++ b/dotdrop/dotdrop.py @@ -80,7 +80,8 @@ def _dotfile_update(o, path, key=False): updater = Updater(o.dotpath, o.variables, o.conf, dry=o.dry, safe=o.safe, debug=o.debug, ignore=o.update_ignore, - showpatch=o.update_showpatch) + showpatch=o.update_showpatch, + ignore_missing_in_dotdrop=o.ignore_missing_in_dotdrop) if key: return updater.update_key(path) return updater.update_path(path) @@ -97,7 +98,8 @@ def _dotfile_compare(o, dotfile, tmp): workdir=o.workdir, debug=o.debug, backup_suffix=o.install_backup_suffix, diff_cmd=o.diff_command) - comp = Comparator(diff_cmd=o.diff_command, debug=o.debug) + comp = Comparator(diff_cmd=o.diff_command, debug=o.debug, + ignore_missing_in_dotdrop=o.ignore_missing_in_dotdrop) # add dotfile variables newvars = dotfile.get_dotfile_variables() diff --git a/dotdrop/options.py b/dotdrop/options.py index 93edd7f..d05cbd8 100644 --- a/dotdrop/options.py +++ b/dotdrop/options.py @@ -43,7 +43,7 @@ OPT_LINK = { LinkTypes.LINK.name.lower(): LinkTypes.LINK, LinkTypes.LINK_CHILDREN.name.lower(): LinkTypes.LINK_CHILDREN} -BANNER = """ _ _ _ +BANNER = r""" _ _ _ __| | ___ | |_ __| |_ __ ___ _ __ / _` |/ _ \| __/ _` | '__/ _ \| '_ | \__,_|\___/ \__\__,_|_| \___/| .__/ v{} @@ -57,9 +57,9 @@ Usage: [-w ] [...] dotdrop import [-Vbdfm] [-c ] [-p ] [-s ] [-l ] [-i ...] ... - dotdrop compare [-LVb] [-c ] [-p ] + dotdrop compare [-LVbz] [-c ] [-p ] [-w ] [-C ...] [-i ...] - dotdrop update [-VbfdkP] [-c ] [-p ] + dotdrop update [-VbfdkPz] [-c ] [-p ] [-w ] [-i ...] [...] dotdrop remove [-Vbfdk] [-c ] [-p ] [...] dotdrop files [-VbTG] [-c ] [-p ] @@ -73,6 +73,7 @@ Options: -b --no-banner Do not display the banner. -c --cfg= Path to the config. -C --file= Path of dotfile to compare. + -z --ignore-missing Ignore files in installed folders that are missing. -d --dry Dry run. -D --showdiff Show a diff before overwriting. -f --force Do not ask user confirmation for anything. @@ -123,6 +124,7 @@ class Options(AttrMonitor): self.log = Logger() self.debug = self.args['--verbose'] or ENV_DEBUG in os.environ self.dry = self.args['--dry'] + self.ignore_missing_in_dotdrop = self.args['--ignore-missing'] if ENV_NODEBUG in os.environ: # force disabling debugs self.debug = False diff --git a/dotdrop/updater.py b/dotdrop/updater.py index 11eda8b..0a36c2b 100644 --- a/dotdrop/updater.py +++ b/dotdrop/updater.py @@ -25,7 +25,8 @@ class Updater: def __init__(self, dotpath, variables, conf, dry=False, safe=True, debug=False, - ignore=[], showpatch=False): + ignore=[], showpatch=False, + ignore_missing_in_dotdrop=False): """constructor @dotpath: path where dotfiles are stored @variables: dictionary of variables for the templates @@ -44,6 +45,7 @@ class Updater: self.debug = debug self.ignore = ignore self.showpatch = showpatch + self.ignore_missing_in_dotdrop = ignore_missing_in_dotdrop self.templater = Templategen(variables=self.variables, base=self.dotpath, debug=self.debug) @@ -403,7 +405,10 @@ class Updater: return True def _ignore(self, paths): - if must_ignore(paths, self.ignores, debug=self.debug): + if must_ignore( + paths, self.ignores, debug=self.debug, + ignore_missing_in_dotdrop=self.ignore_missing_in_dotdrop, + ): if self.debug: self.log.dbg('ignoring update for {}'.format(paths)) return True diff --git a/dotdrop/utils.py b/dotdrop/utils.py index 9189cda..c6a3a49 100644 --- a/dotdrop/utils.py +++ b/dotdrop/utils.py @@ -198,8 +198,12 @@ def strip_home(path): return path -def must_ignore(paths, ignores, debug=False): +def must_ignore(paths, ignores, debug=False, ignore_missing_in_dotdrop=False): """return true if any paths in list matches any ignore patterns""" + if ignore_missing_in_dotdrop and \ + len(paths) >= 2 and not os.path.exists(paths[1]): + return True + if not ignores: return False if debug: diff --git a/tests/helpers.py b/tests/helpers.py index d080e9e..c29fe62 100644 --- a/tests/helpers.py +++ b/tests/helpers.py @@ -135,6 +135,7 @@ def _fake_args(): args['--file-only'] = False args['--workers'] = 1 args['--preserve-mode'] = False + args['--ignore-missing'] = False # cmds args['profiles'] = False args['files'] = False From 4a201487825e3a1af8426768efed65574117b1db Mon Sep 17 00:00:00 2001 From: Marcel Robitaille Date: Fri, 11 Dec 2020 01:36:32 -0500 Subject: [PATCH 02/15] Fix line length --- dotdrop/comparator.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dotdrop/comparator.py b/dotdrop/comparator.py index 6159afb..39e7cde 100644 --- a/dotdrop/comparator.py +++ b/dotdrop/comparator.py @@ -16,7 +16,8 @@ from dotdrop.utils import must_ignore, uniq_list, diff, \ class Comparator: - def __init__(self, diff_cmd='', debug=False, ignore_missing_in_dotdrop=False): + def __init__(self, diff_cmd='', debug=False, + ignore_missing_in_dotdrop=False): """constructor @diff_cmd: diff command to use @debug: enable debug From b4d7791a608ec123dad206ee11b14c03641e41a5 Mon Sep 17 00:00:00 2001 From: Marcel Robitaille Date: Sat, 12 Dec 2020 00:26:14 -0500 Subject: [PATCH 03/15] Add global `ignore_missing_in_dotdrop` option --- dotdrop/options.py | 3 ++- dotdrop/settings.py | 6 +++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/dotdrop/options.py b/dotdrop/options.py index d05cbd8..c8d001d 100644 --- a/dotdrop/options.py +++ b/dotdrop/options.py @@ -124,7 +124,6 @@ class Options(AttrMonitor): self.log = Logger() self.debug = self.args['--verbose'] or ENV_DEBUG in os.environ self.dry = self.args['--dry'] - self.ignore_missing_in_dotdrop = self.args['--ignore-missing'] if ENV_NODEBUG in os.environ: # force disabling debugs self.debug = False @@ -266,6 +265,8 @@ class Options(AttrMonitor): self.compare_ignore.append('*{}'.format(self.install_backup_suffix)) self.compare_ignore = uniq_list(self.compare_ignore) self.compare_fileonly = self.args['--file-only'] + self.ignore_missing_in_dotdrop = self.ignore_missing_in_dotdrop or \ + self.args['--ignore-missing'] # "import" specifics self.import_path = self.args[''] diff --git a/dotdrop/settings.py b/dotdrop/settings.py index faa28a3..038ff5e 100644 --- a/dotdrop/settings.py +++ b/dotdrop/settings.py @@ -41,6 +41,7 @@ class Settings(DictParser): key_filter_file = 'filter_file' key_diff_command = 'diff_command' key_template_dotfile_default = 'template_dotfile_default' + key_ignore_missing_in_dotdrop = 'ignore_missing_in_dotdrop' # import keys key_import_actions = 'import_actions' @@ -57,7 +58,8 @@ class Settings(DictParser): workdir='~/.config/dotdrop', showdiff=False, minversion=None, func_file=[], filter_file=[], diff_command='diff -r -u {0} {1}', - template_dotfile_default=True): + template_dotfile_default=True, + ignore_missing_in_dotdrop=False): self.backup = backup self.banner = banner self.create = create @@ -84,6 +86,7 @@ class Settings(DictParser): self.filter_file = filter_file self.diff_command = diff_command self.template_dotfile_default = template_dotfile_default + self.ignore_missing_in_dotdrop = ignore_missing_in_dotdrop def _serialize_seq(self, name, dic): """serialize attribute 'name' into 'dic'""" @@ -107,6 +110,7 @@ class Settings(DictParser): self.key_minversion: self.minversion, self.key_diff_command: self.diff_command, self.key_template_dotfile_default: self.template_dotfile_default, + self.key_ignore_missing_in_dotdrop: self.ignore_missing_in_dotdrop, } self._serialize_seq(self.key_default_actions, dic) self._serialize_seq(self.key_import_actions, dic) From fdc6c91e0835518a9598e30425eba478899b801f Mon Sep 17 00:00:00 2001 From: Marcel Robitaille Date: Sat, 12 Dec 2020 00:41:05 -0500 Subject: [PATCH 04/15] Add dotfile-specific `ignore_missing` option --- dotdrop/cfg_yaml.py | 1 + dotdrop/dotdrop.py | 4 +++- dotdrop/dotfile.py | 4 +++- dotdrop/updater.py | 40 +++++++++++++++++++++------------------- 4 files changed, 28 insertions(+), 21 deletions(-) diff --git a/dotdrop/cfg_yaml.py b/dotdrop/cfg_yaml.py index 16461c5..9b14b12 100644 --- a/dotdrop/cfg_yaml.py +++ b/dotdrop/cfg_yaml.py @@ -59,6 +59,7 @@ class CfgYaml: key_dotfile_noempty = 'ignoreempty' key_dotfile_template = 'template' key_dotfile_chmod = 'chmod' + key_dotfile_ignore_missing = 'ignore_missing_in_dotdrop' # profile key_profile_dotfiles = 'dotfiles' diff --git a/dotdrop/dotdrop.py b/dotdrop/dotdrop.py index 49c79e0..0d0f346 100644 --- a/dotdrop/dotdrop.py +++ b/dotdrop/dotdrop.py @@ -93,13 +93,15 @@ def _dotfile_compare(o, dotfile, tmp): returns True if same """ t = _get_templater(o) + ignore_missing_in_dotdrop = o.ignore_missing_in_dotdrop or \ + dotfile.ignore_missing_in_dotdrop inst = Installer(create=o.create, backup=o.backup, dry=o.dry, base=o.dotpath, workdir=o.workdir, debug=o.debug, backup_suffix=o.install_backup_suffix, diff_cmd=o.diff_command) comp = Comparator(diff_cmd=o.diff_command, debug=o.debug, - ignore_missing_in_dotdrop=o.ignore_missing_in_dotdrop) + ignore_missing_in_dotdrop=ignore_missing_in_dotdrop) # add dotfile variables newvars = dotfile.get_dotfile_variables() diff --git a/dotdrop/dotfile.py b/dotdrop/dotfile.py index 9c96434..21a6557 100644 --- a/dotdrop/dotfile.py +++ b/dotdrop/dotfile.py @@ -22,7 +22,8 @@ class Dotfile(DictParser): actions=[], trans_r=None, trans_w=None, link=LinkTypes.NOLINK, noempty=False, cmpignore=[], upignore=[], - instignore=[], template=True, chmod=None): + instignore=[], template=True, chmod=None, + ignore_missing_in_dotdrop=False): """ constructor @key: dotfile key @@ -52,6 +53,7 @@ class Dotfile(DictParser): self.instignore = instignore self.template = template self.chmod = chmod + self.ignore_missing_in_dotdrop = ignore_missing_in_dotdrop if self.link != LinkTypes.NOLINK and \ ( diff --git a/dotdrop/updater.py b/dotdrop/updater.py index 0a36c2b..8bdd185 100644 --- a/dotdrop/updater.py +++ b/dotdrop/updater.py @@ -108,7 +108,7 @@ class Updater: self.log.err(msg.format(dtpath)) return False - if self._ignore([path, dtpath]): + if self._ignore([path, dtpath], dotfile): self.log.sub('\"{}\" ignored'.format(dotfile.key)) return True # apply write transformation if any @@ -122,9 +122,9 @@ class Updater: # handle the pointed file if os.path.isdir(new_path): - ret = self._handle_dir(new_path, dtpath) + ret = self._handle_dir(new_path, dtpath, dotfile) else: - ret = self._handle_file(new_path, dtpath) + ret = self._handle_file(new_path, dtpath, dotfile) if fsmode != dfmode: # mirror rights @@ -205,9 +205,9 @@ class Updater: except OSError as e: self.log.err(e) - def _handle_file(self, path, dtpath, compare=True): + def _handle_file(self, path, dtpath, dotfile, compare=True): """sync path (deployed file) and dtpath (dotdrop dotfile path)""" - if self._ignore([path, dtpath]): + if self._ignore([path, dtpath], dotfile): self.log.sub('\"{}\" ignored'.format(dtpath)) return True if self.debug: @@ -245,29 +245,29 @@ class Updater: return False return True - def _handle_dir(self, path, dtpath): + def _handle_dir(self, path, dtpath, dotfile): """sync path (deployed dir) and dtpath (dotdrop dir path)""" if self.debug: self.log.dbg('handle update for dir {} to {}'.format(path, dtpath)) # paths must be absolute (no tildes) path = os.path.expanduser(path) dtpath = os.path.expanduser(dtpath) - if self._ignore([path, dtpath]): + if self._ignore([path, dtpath], dotfile): self.log.sub('\"{}\" ignored'.format(dtpath)) return True # find the differences diff = filecmp.dircmp(path, dtpath, ignore=None) # handle directories diff - ret = self._merge_dirs(diff) + ret = self._merge_dirs(diff, dotfile) self._mirror_rights(path, dtpath) return ret - def _merge_dirs(self, diff): + def _merge_dirs(self, diff, dotfile): """Synchronize directories recursively.""" left, right = diff.left, diff.right if self.debug: self.log.dbg('sync dir {} to {}'.format(left, right)) - if self._ignore([left, right]): + if self._ignore([left, right], dotfile): return True # create dirs that don't exist in dotdrop @@ -278,7 +278,7 @@ class Updater: continue # match to dotdrop dotpath new = os.path.join(right, toadd) - if self._ignore([exist, new]): + if self._ignore([exist, new], dotfile): self.log.sub('\"{}\" ignored'.format(exist)) continue if self.dry: @@ -311,7 +311,7 @@ class Updater: if not os.path.isdir(old): # ignore files for now continue - if self._ignore([old]): + if self._ignore([old], dotfile): continue if self.dry: self.log.dry('would rm -r {}'.format(old)) @@ -331,14 +331,14 @@ class Updater: for f in fdiff: fleft = os.path.join(left, f) fright = os.path.join(right, f) - if self._ignore([fleft, fright]): + if self._ignore([fleft, fright], dotfile): continue if self.dry: self.log.dry('would cp {} {}'.format(fleft, fright)) continue if self.debug: self.log.dbg('cp {} {}'.format(fleft, fright)) - self._handle_file(fleft, fright, compare=False) + self._handle_file(fleft, fright, dotfile, compare=False) # copy files that don't exist in dotdrop for toadd in diff.left_only: @@ -347,7 +347,7 @@ class Updater: # ignore dirs, done above continue new = os.path.join(right, toadd) - if self._ignore([exist, new]): + if self._ignore([exist, new], dotfile): continue if self.dry: self.log.dry('would cp {} {}'.format(exist, new)) @@ -366,7 +366,7 @@ class Updater: if os.path.isdir(new): # ignore dirs, done above continue - if self._ignore([new]): + if self._ignore([new], dotfile): continue if self.dry: self.log.dry('would rm {}'.format(new)) @@ -385,7 +385,7 @@ class Updater: # Recursively decent into common subdirectories. for subdir in diff.subdirs.values(): - self._merge_dirs(subdir) + self._merge_dirs(subdir, dotfile) # Nothing more to do here. return True @@ -404,10 +404,12 @@ class Updater: return False return True - def _ignore(self, paths): + def _ignore(self, paths, dotfile): + ignore_missing_in_dotdrop = self.ignore_missing_in_dotdrop or \ + dotfile.ignore_missing_in_dotdrop if must_ignore( paths, self.ignores, debug=self.debug, - ignore_missing_in_dotdrop=self.ignore_missing_in_dotdrop, + ignore_missing_in_dotdrop=ignore_missing_in_dotdrop, ): if self.debug: self.log.dbg('ignoring update for {}'.format(paths)) From 41114c81bec4dbb51804fba2765ac15a4e6a0926 Mon Sep 17 00:00:00 2001 From: Marcel Robitaille Date: Mon, 14 Dec 2020 11:19:35 -0500 Subject: [PATCH 05/15] Clarify variable names in comparator --- dotdrop/comparator.py | 144 ++++++++++++++++++++++++++---------------- dotdrop/updater.py | 116 +++++++++++++++++++--------------- dotdrop/utils.py | 6 +- 3 files changed, 154 insertions(+), 112 deletions(-) diff --git a/dotdrop/comparator.py b/dotdrop/comparator.py index 39e7cde..2852def 100644 --- a/dotdrop/comparator.py +++ b/dotdrop/comparator.py @@ -27,105 +27,135 @@ class Comparator: self.log = Logger() self.ignore_missing_in_dotdrop = ignore_missing_in_dotdrop - def compare(self, left, right, ignore=[]): - """diff left (dotdrop dotfile) and right (deployed file)""" - left = os.path.expanduser(left) - right = os.path.expanduser(right) + def compare(self, local_path, deployed_path, ignore=[]): + """diff local_path (dotdrop dotfile) and + deployed_path (destination file)""" + local_path = os.path.expanduser(local_path) + deployed_path = os.path.expanduser(deployed_path) if self.debug: - self.log.dbg('comparing {} and {}'.format(left, right)) + self.log.dbg('comparing {} and {}'.format( + local_path, + deployed_path, + )) self.log.dbg('ignore pattern(s): {}'.format(ignore)) # test type of file - if os.path.isdir(left) and not os.path.isdir(right): - return '\"{}\" is a dir while \"{}\" is a file\n'.format(left, - right) - if not os.path.isdir(left) and os.path.isdir(right): - return '\"{}\" is a file while \"{}\" is a dir\n'.format(left, - right) + if os.path.isdir(local_path) and not os.path.isdir(deployed_path): + return '\"{}\" is a dir while \"{}\" is a file\n'.format( + local_path, + deployed_path, + ) + if not os.path.isdir(local_path) and os.path.isdir(deployed_path): + return '\"{}\" is a file while \"{}\" is a dir\n'.format( + local_path, + deployed_path, + ) # test content - if not os.path.isdir(left): + if not os.path.isdir(local_path): if self.debug: - self.log.dbg('{} is a file'.format(left)) + self.log.dbg('{} is a file'.format(local_path)) if self.debug: self.log.dbg('is file') - ret = self._comp_file(left, right, ignore) + ret = self._comp_file(local_path, deployed_path, ignore) if not ret: - ret = self._comp_mode(left, right) + ret = self._comp_mode(local_path, deployed_path) return ret if self.debug: - self.log.dbg('{} is a directory'.format(left)) + self.log.dbg('{} is a directory'.format(local_path)) - ret = self._comp_dir(left, right, ignore) + ret = self._comp_dir(local_path, deployed_path, ignore) if not ret: - ret = self._comp_mode(left, right) + ret = self._comp_mode(local_path, deployed_path) return ret - def _comp_mode(self, left, right): + def _comp_mode(self, local_path, deployed_path): """compare mode""" - left_mode = get_file_perm(left) - right_mode = get_file_perm(right) - if left_mode == right_mode: + local_mode = get_file_perm(local_path) + deployed_mode = get_file_perm(deployed_path) + if local_mode == deployed_mode: return '' if self.debug: msg = 'mode differ {} ({:o}) and {} ({:o})' - self.log.dbg(msg.format(left, left_mode, right, right_mode)) + self.log.dbg(msg.format(local_path, local_mode, deployed_path, + deployed_mode)) ret = 'modes differ for {} ({:o}) vs {:o}\n' - return ret.format(right, right_mode, left_mode) + return ret.format(deployed_path, deployed_mode, local_mode) - def _comp_file(self, left, right, ignore): + def _comp_file(self, local_path, deployed_path, ignore): """compare a file""" if self.debug: - self.log.dbg('compare file {} with {}'.format(left, right)) - if must_ignore([left, right], ignore, debug=self.debug): + self.log.dbg('compare file {} with {}'.format( + local_path, + deployed_path, + )) + if (self.ignore_missing_in_dotdrop and not os.path.exists(local_path)) \ + or must_ignore([local_path, deployed_path], ignore, + debug=self.debug): if self.debug: - self.log.dbg('ignoring diff {} and {}'.format(left, right)) + self.log.dbg('ignoring diff {} and {}'.format( + local_path, + deployed_path, + )) return '' - return self._diff(left, right) + return self._diff(local_path, deployed_path) - def _comp_dir(self, left, right, ignore): + def _comp_dir(self, local_path, deployed_path, ignore): """compare a directory""" if self.debug: - self.log.dbg('compare directory {} with {}'.format(left, right)) - if not os.path.exists(right): + self.log.dbg('compare directory {} with {}'.format( + local_path, + deployed_path, + )) + if not os.path.exists(deployed_path): return '' - if must_ignore([left, right], ignore, debug=self.debug): + if (self.ignore_missing_in_dotdrop and not os.path.exists(local_path)) \ + or must_ignore([local_path, deployed_path], ignore, + debug=self.debug): if self.debug: - self.log.dbg('ignoring diff {} and {}'.format(left, right)) + self.log.dbg('ignoring diff {} and {}'.format( + local_path, + deployed_path, + )) return '' - if not os.path.isdir(right): - return '\"{}\" is a file\n'.format(right) + if not os.path.isdir(deployed_path): + return '\"{}\" is a file\n'.format(deployed_path) if self.debug: - self.log.dbg('compare {} and {}'.format(left, right)) + self.log.dbg('compare {} and {}'.format(local_path, deployed_path)) ret = [] - comp = filecmp.dircmp(left, right) + comp = filecmp.dircmp(local_path, deployed_path) # handle files only in deployed dir for i in comp.left_only: - if must_ignore([os.path.join(left, i)], + if self.ignore_missing_in_dotdrop: + continue + if must_ignore([os.path.join(local_path, i)], ignore, debug=self.debug): continue ret.append('=> \"{}\" does not exist on destination\n'.format(i)) # handle files only in dotpath dir for i in comp.right_only: - if must_ignore([os.path.join(right, i)], + if must_ignore([os.path.join(deployed_path, i)], ignore, debug=self.debug): continue if not self.ignore_missing_in_dotdrop: ret.append('=> \"{}\" does not exist in dotdrop\n'.format(i)) - # same left and right but different type + # same local_path and deployed_path but different type funny = comp.common_funny for i in funny: - lfile = os.path.join(left, i) - rfile = os.path.join(right, i) - if must_ignore([lfile, rfile], + source_file = os.path.join(local_path, i) + deployed_file = os.path.join(deployed_path, i) + if self.ignore_missing_in_dotdrop and \ + not os.path.exists(source_file): + continue + if must_ignore([source_file, deployed_file], ignore, debug=self.debug): continue - short = os.path.basename(lfile) + short = os.path.basename(source_file) # file vs dir ret.append('=> different type: \"{}\"\n'.format(short)) @@ -134,27 +164,29 @@ class Comparator: funny.extend(comp.funny_files) funny = uniq_list(funny) for i in funny: - lfile = os.path.join(left, i) - rfile = os.path.join(right, i) - if must_ignore([lfile, rfile], + source_file = os.path.join(local_path, i) + deployed_file = os.path.join(deployed_path, i) + if self.ignore_missing_in_dotdrop and \ + not os.path.exists(source_file): + continue + if must_ignore([source_file, deployed_file], ignore, debug=self.debug): continue - diff = self._diff(lfile, rfile, header=True) - ret.append(diff) + ret.append(self._diff(source_file, deployed_file, header=True)) # recursively compare subdirs for i in comp.common_dirs: - subleft = os.path.join(left, i) - subright = os.path.join(right, i) - ret.extend(self._comp_dir(subleft, subright, ignore)) + sublocal_path = os.path.join(local_path, i) + subdeployed_path = os.path.join(deployed_path, i) + ret.extend(self._comp_dir(sublocal_path, subdeployed_path, ignore)) return ''.join(ret) - def _diff(self, left, right, header=False): + def _diff(self, local_path, deployed_path, header=False): """diff two files""" - out = diff(modified=left, original=right, + out = diff(modified=local_path, original=deployed_path, diff_cmd=self.diff_cmd, debug=self.debug) if header: - lshort = os.path.basename(left) + lshort = os.path.basename(local_path) out = '=> diff \"{}\":\n{}'.format(lshort, out) return out diff --git a/dotdrop/updater.py b/dotdrop/updater.py index 8bdd185..2cbabb5 100644 --- a/dotdrop/updater.py +++ b/dotdrop/updater.py @@ -94,49 +94,52 @@ class Updater: if self.debug: self.log.dbg('ignore pattern(s): {}'.format(self.ignores)) - path = os.path.expanduser(path) - dtpath = os.path.join(self.dotpath, dotfile.src) - dtpath = os.path.expanduser(dtpath) + deployed_path = os.path.expanduser(path) + local_path = os.path.join(self.dotpath, dotfile.src) + local_path = os.path.expanduser(local_path) - if not os.path.exists(path): + if not os.path.exists(deployed_path): msg = '\"{}\" does not exist' - self.log.err(msg.format(path)) + self.log.err(msg.format(deployed_path)) return False - if not os.path.exists(dtpath): + if not os.path.exists(local_path): msg = '\"{}\" does not exist, import it first' - self.log.err(msg.format(dtpath)) + self.log.err(msg.format(local_path)) return False - if self._ignore([path, dtpath], dotfile): + ignore_missing_in_dotdrop = self.ignore_missing_in_dotdrop or \ + dotfile.ignore_missing_in_dotdrop + if (ignore_missing_in_dotdrop and not os.path.exists(local_path)) or \ + self._ignore([deployed_path, local_path], dotfile): self.log.sub('\"{}\" ignored'.format(dotfile.key)) return True # apply write transformation if any - new_path = self._apply_trans_w(path, dotfile) + new_path = self._apply_trans_w(deployed_path, dotfile) if not new_path: return False # save current rights - fsmode = get_file_perm(path) - dfmode = get_file_perm(dtpath) + deployed_mode = get_file_perm(deployed_path) + local_mode = get_file_perm(local_path) # handle the pointed file if os.path.isdir(new_path): - ret = self._handle_dir(new_path, dtpath, dotfile) + ret = self._handle_dir(new_path, local_path, dotfile) else: - ret = self._handle_file(new_path, dtpath, dotfile) + ret = self._handle_file(new_path, local_path, dotfile) - if fsmode != dfmode: + if deployed_mode != local_mode: # mirror rights if self.debug: m = 'adopt mode {:o} for {}' - self.log.dbg(m.format(fsmode, dotfile.key)) - r = self.conf.update_dotfile(dotfile.key, fsmode) + self.log.dbg(m.format(deployed_mode, dotfile.key)) + r = self.conf.update_dotfile(dotfile.key, deployed_mode) if r: ret = True # clean temporary files - if new_path != path and os.path.exists(new_path): + if new_path != deployed_path and os.path.exists(new_path): removepath(new_path, logger=self.log) return ret @@ -205,61 +208,77 @@ class Updater: except OSError as e: self.log.err(e) - def _handle_file(self, path, dtpath, dotfile, compare=True): - """sync path (deployed file) and dtpath (dotdrop dotfile path)""" - if self._ignore([path, dtpath], dotfile): - self.log.sub('\"{}\" ignored'.format(dtpath)) + def _handle_file(self, deployed_path, local_path, dotfile, compare=True): + """sync path (deployed file) and local_path (dotdrop dotfile path)""" + if self._ignore([deployed_path, local_path], dotfile): + self.log.sub('\"{}\" ignored'.format(local_path)) return True if self.debug: - self.log.dbg('update for file {} and {}'.format(path, dtpath)) - if self._is_template(dtpath): + self.log.dbg('update for file {} and {}'.format( + deployed_path, + local_path, + )) + if self._is_template(local_path): # dotfile is a template if self.debug: - self.log.dbg('{} is a template'.format(dtpath)) + self.log.dbg('{} is a template'.format(local_path)) if self.showpatch: try: - self._show_patch(path, dtpath) + self._show_patch(deployed_path, local_path) except UndefinedException as e: - msg = 'unable to show patch for {}: {}'.format(path, e) + msg = 'unable to show patch for {}: {}'.format( + deployed_path, + e, + ) self.log.warn(msg) return False - if compare and filecmp.cmp(path, dtpath, shallow=False) and \ - self._same_rights(path, dtpath): + if compare and \ + filecmp.cmp(deployed_path, local_path, shallow=False) and \ + self._same_rights(deployed_path, local_path): # no difference if self.debug: - self.log.dbg('identical files: {} and {}'.format(path, dtpath)) + self.log.dbg('identical files: {} and {}'.format( + deployed_path, + local_path, + )) return True - if not self._overwrite(path, dtpath): + if not self._overwrite(deployed_path, local_path): return False try: if self.dry: - self.log.dry('would cp {} {}'.format(path, dtpath)) + self.log.dry('would cp {} {}'.format(deployed_path, local_path)) else: if self.debug: - self.log.dbg('cp {} {}'.format(path, dtpath)) - shutil.copyfile(path, dtpath) - self._mirror_rights(path, dtpath) - self.log.sub('\"{}\" updated'.format(dtpath)) + self.log.dbg('cp {} {}'.format(deployed_path, local_path)) + shutil.copyfile(deployed_path, local_path) + self._mirror_rights(deployed_path, local_path) + self.log.sub('\"{}\" updated'.format(local_path)) except IOError as e: - self.log.warn('{} update failed, do manually: {}'.format(path, e)) + self.log.warn('{} update failed, do manually: {}'.format( + deployed_path, + e + )) return False return True - def _handle_dir(self, path, dtpath, dotfile): - """sync path (deployed dir) and dtpath (dotdrop dir path)""" + def _handle_dir(self, deployed_path, local_path, dotfile): + """sync path (local dir) and local_path (dotdrop dir path)""" if self.debug: - self.log.dbg('handle update for dir {} to {}'.format(path, dtpath)) + self.log.dbg('handle update for dir {} to {}'.format( + deployed_path, + local_path, + )) # paths must be absolute (no tildes) - path = os.path.expanduser(path) - dtpath = os.path.expanduser(dtpath) - if self._ignore([path, dtpath], dotfile): - self.log.sub('\"{}\" ignored'.format(dtpath)) + path = os.path.expanduser(deployed_path) + local_path = os.path.expanduser(local_path) + if self._ignore([path, local_path], dotfile): + self.log.sub('\"{}\" ignored'.format(local_path)) return True # find the differences - diff = filecmp.dircmp(path, dtpath, ignore=None) + diff = filecmp.dircmp(path, local_path, ignore=None) # handle directories diff ret = self._merge_dirs(diff, dotfile) - self._mirror_rights(path, dtpath) + self._mirror_rights(path, local_path) return ret def _merge_dirs(self, diff, dotfile): @@ -405,12 +424,7 @@ class Updater: return True def _ignore(self, paths, dotfile): - ignore_missing_in_dotdrop = self.ignore_missing_in_dotdrop or \ - dotfile.ignore_missing_in_dotdrop - if must_ignore( - paths, self.ignores, debug=self.debug, - ignore_missing_in_dotdrop=ignore_missing_in_dotdrop, - ): + 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 c6a3a49..9189cda 100644 --- a/dotdrop/utils.py +++ b/dotdrop/utils.py @@ -198,12 +198,8 @@ def strip_home(path): return path -def must_ignore(paths, ignores, debug=False, ignore_missing_in_dotdrop=False): +def must_ignore(paths, ignores, debug=False): """return true if any paths in list matches any ignore patterns""" - if ignore_missing_in_dotdrop and \ - len(paths) >= 2 and not os.path.exists(paths[1]): - return True - if not ignores: return False if debug: From 6f826b1d96624e87ecb55054e0697657a09549ac Mon Sep 17 00:00:00 2001 From: Marcel Robitaille Date: Mon, 14 Dec 2020 11:49:50 -0500 Subject: [PATCH 06/15] Fix defined outside constructor --- dotdrop/updater.py | 1 + 1 file changed, 1 insertion(+) diff --git a/dotdrop/updater.py b/dotdrop/updater.py index 2cbabb5..b84a14f 100644 --- a/dotdrop/updater.py +++ b/dotdrop/updater.py @@ -44,6 +44,7 @@ class Updater: self.safe = safe self.debug = debug self.ignore = ignore + self.ignores = None self.showpatch = showpatch self.ignore_missing_in_dotdrop = ignore_missing_in_dotdrop self.templater = Templategen(variables=self.variables, From 9211c215bf3d85c72bfa30b4b9c3c95084d0da22 Mon Sep 17 00:00:00 2001 From: Marcel Robitaille Date: Mon, 14 Dec 2020 12:02:53 -0500 Subject: [PATCH 07/15] Remove useless method argument I don't need this argument anymore --- dotdrop/updater.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/dotdrop/updater.py b/dotdrop/updater.py index b84a14f..39f697e 100644 --- a/dotdrop/updater.py +++ b/dotdrop/updater.py @@ -112,7 +112,7 @@ class Updater: ignore_missing_in_dotdrop = self.ignore_missing_in_dotdrop or \ dotfile.ignore_missing_in_dotdrop if (ignore_missing_in_dotdrop and not os.path.exists(local_path)) or \ - self._ignore([deployed_path, local_path], dotfile): + self._ignore([deployed_path, local_path]): self.log.sub('\"{}\" ignored'.format(dotfile.key)) return True # apply write transformation if any @@ -211,7 +211,7 @@ class Updater: def _handle_file(self, deployed_path, local_path, dotfile, compare=True): """sync path (deployed file) and local_path (dotdrop dotfile path)""" - if self._ignore([deployed_path, local_path], dotfile): + if self._ignore([deployed_path, local_path]): self.log.sub('\"{}\" ignored'.format(local_path)) return True if self.debug: @@ -272,7 +272,7 @@ class Updater: # paths must be absolute (no tildes) path = os.path.expanduser(deployed_path) local_path = os.path.expanduser(local_path) - if self._ignore([path, local_path], dotfile): + if self._ignore([path, local_path]): self.log.sub('\"{}\" ignored'.format(local_path)) return True # find the differences @@ -287,7 +287,7 @@ class Updater: left, right = diff.left, diff.right if self.debug: self.log.dbg('sync dir {} to {}'.format(left, right)) - if self._ignore([left, right], dotfile): + if self._ignore([left, right]): return True # create dirs that don't exist in dotdrop @@ -298,7 +298,7 @@ class Updater: continue # match to dotdrop dotpath new = os.path.join(right, toadd) - if self._ignore([exist, new], dotfile): + if self._ignore([exist, new]): self.log.sub('\"{}\" ignored'.format(exist)) continue if self.dry: @@ -331,7 +331,7 @@ class Updater: if not os.path.isdir(old): # ignore files for now continue - if self._ignore([old], dotfile): + if self._ignore([old]): continue if self.dry: self.log.dry('would rm -r {}'.format(old)) @@ -351,7 +351,7 @@ class Updater: for f in fdiff: fleft = os.path.join(left, f) fright = os.path.join(right, f) - if self._ignore([fleft, fright], dotfile): + if self._ignore([fleft, fright]): continue if self.dry: self.log.dry('would cp {} {}'.format(fleft, fright)) @@ -367,7 +367,7 @@ class Updater: # ignore dirs, done above continue new = os.path.join(right, toadd) - if self._ignore([exist, new], dotfile): + if self._ignore([exist, new]): continue if self.dry: self.log.dry('would cp {} {}'.format(exist, new)) @@ -386,7 +386,7 @@ class Updater: if os.path.isdir(new): # ignore dirs, done above continue - if self._ignore([new], dotfile): + if self._ignore([new]): continue if self.dry: self.log.dry('would rm {}'.format(new)) @@ -424,7 +424,7 @@ class Updater: return False return True - def _ignore(self, paths, dotfile): + def _ignore(self, paths): if must_ignore(paths, self.ignores, debug=self.debug): if self.debug: self.log.dbg('ignoring update for {}'.format(paths)) From d2f07ade5dc69cc890e1bc92ac1c9cfb20bd3af3 Mon Sep 17 00:00:00 2001 From: Marcel Robitaille Date: Mon, 14 Dec 2020 12:32:37 -0500 Subject: [PATCH 08/15] Fix line length --- dotdrop/comparator.py | 6 ++++-- dotdrop/updater.py | 5 ++++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/dotdrop/comparator.py b/dotdrop/comparator.py index 2852def..05b0590 100644 --- a/dotdrop/comparator.py +++ b/dotdrop/comparator.py @@ -90,7 +90,8 @@ class Comparator: local_path, deployed_path, )) - if (self.ignore_missing_in_dotdrop and not os.path.exists(local_path)) \ + if (self.ignore_missing_in_dotdrop and not + os.path.exists(local_path)) \ or must_ignore([local_path, deployed_path], ignore, debug=self.debug): if self.debug: @@ -110,7 +111,8 @@ class Comparator: )) if not os.path.exists(deployed_path): return '' - if (self.ignore_missing_in_dotdrop and not os.path.exists(local_path)) \ + if (self.ignore_missing_in_dotdrop and not + os.path.exists(local_path)) \ or must_ignore([local_path, deployed_path], ignore, debug=self.debug): if self.debug: diff --git a/dotdrop/updater.py b/dotdrop/updater.py index 39f697e..6a083ad 100644 --- a/dotdrop/updater.py +++ b/dotdrop/updater.py @@ -247,7 +247,10 @@ class Updater: return False try: if self.dry: - self.log.dry('would cp {} {}'.format(deployed_path, local_path)) + self.log.dry('would cp {} {}'.format( + deployed_path, + local_path, + )) else: if self.debug: self.log.dbg('cp {} {}'.format(deployed_path, local_path)) From ecc4fe3c19c29e0ed2c7a52cd401958a925ee28c Mon Sep 17 00:00:00 2001 From: Marcel Robitaille Date: Sat, 6 Feb 2021 22:38:27 -0500 Subject: [PATCH 09/15] Actually ignore missing files on update --- dotdrop/updater.py | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/dotdrop/updater.py b/dotdrop/updater.py index 6a083ad..3c0c44e 100644 --- a/dotdrop/updater.py +++ b/dotdrop/updater.py @@ -273,16 +273,17 @@ class Updater: local_path, )) # paths must be absolute (no tildes) - path = os.path.expanduser(deployed_path) + deployed_path = os.path.expanduser(deployed_path) local_path = os.path.expanduser(local_path) - if self._ignore([path, local_path]): + + if self._ignore([deployed_path, local_path]): self.log.sub('\"{}\" ignored'.format(local_path)) return True # find the differences - diff = filecmp.dircmp(path, local_path, ignore=None) + diff = filecmp.dircmp(deployed_path, local_path, ignore=None) # handle directories diff ret = self._merge_dirs(diff, dotfile) - self._mirror_rights(path, local_path) + self._mirror_rights(deployed_path, local_path) return ret def _merge_dirs(self, diff, dotfile): @@ -293,6 +294,9 @@ class Updater: if self._ignore([left, right]): return True + ignore_missing_in_dotdrop = self.ignore_missing_in_dotdrop or \ + dotfile.ignore_missing_in_dotdrop + # create dirs that don't exist in dotdrop for toadd in diff.left_only: exist = os.path.join(left, toadd) @@ -301,7 +305,8 @@ class Updater: continue # match to dotdrop dotpath new = os.path.join(right, toadd) - if self._ignore([exist, new]): + if (ignore_missing_in_dotdrop and not os.path.exists(new)) or \ + self._ignore([exist, new]): self.log.sub('\"{}\" ignored'.format(exist)) continue if self.dry: @@ -354,7 +359,8 @@ class Updater: for f in fdiff: fleft = os.path.join(left, f) fright = os.path.join(right, f) - if self._ignore([fleft, fright]): + if (ignore_missing_in_dotdrop and not os.path.exists(fright)) or \ + self._ignore([exist, new]): continue if self.dry: self.log.dry('would cp {} {}'.format(fleft, fright)) @@ -370,7 +376,8 @@ class Updater: # ignore dirs, done above continue new = os.path.join(right, toadd) - if self._ignore([exist, new]): + if (ignore_missing_in_dotdrop and not os.path.exists(new)) or \ + self._ignore([exist, new]): continue if self.dry: self.log.dry('would cp {} {}'.format(exist, new)) From b8457dfceefd8b04f8036c976bc69d2506dadfda Mon Sep 17 00:00:00 2001 From: Marcel Robitaille Date: Sat, 6 Feb 2021 22:38:44 -0500 Subject: [PATCH 10/15] Ignore venv folder --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 68e4a46..391638e 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,5 @@ stage/ # Source archive packed by `snapcraft cleanbuild` before pushing to the LXD container /packages/*_source.tar.bz2 + +venv From bb586d60839310d8b6de08b9e6ac46f444c2831d Mon Sep 17 00:00:00 2001 From: Marcel Robitaille Date: Sat, 6 Feb 2021 22:38:54 -0500 Subject: [PATCH 11/15] Add tests for ignore-missing on update --- tests-ng/update-ignore-missing.sh | 168 ++++++++++++++++++++++++++++++ 1 file changed, 168 insertions(+) create mode 100755 tests-ng/update-ignore-missing.sh diff --git a/tests-ng/update-ignore-missing.sh b/tests-ng/update-ignore-missing.sh new file mode 100755 index 0000000..b4bf7b8 --- /dev/null +++ b/tests-ng/update-ignore-missing.sh @@ -0,0 +1,168 @@ +#!/usr/bin/env bash +# author: deadc0de6 (https://github.com/deadc0de6) +# Copyright (c) 2017, deadc0de6 +# +# test missing files ignored as expected +# 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}")") + +# 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 +} + +# dotdrop directory +tmps=`mktemp -d --suffix='-dotdrop-tests-source' || mktemp -d` +dt="${tmps}/dotfiles" +mkdir -p ${dt}/folder +touch ${dt}/folder/a + +# fs dotfiles +tmpd=`mktemp -d --suffix='-dotdrop-tests-dest' || mktemp -d` +cp -r ${dt}/folder ${tmpd}/ +touch ${tmpd}/folder/b +mkdir ${tmpd}/folder/c + +# create the config file +cfg="${tmps}/config.yaml" +cat > ${cfg} << _EOF +config: + backup: false + create: true + dotpath: dotfiles +dotfiles: + thedotfile: + dst: ${tmpd}/folder + src: folder +profiles: + p1: + dotfiles: + - thedotfile +_EOF +#cat ${cfg} + +#tree ${dt} + +# +# Test with no ignore-missing setting +# + +# file b / folder c SHOULD be copied +echo "[+] test with no ignore-missing setting" +cd ${ddpath} | ${bin} update -f -c ${cfg} --verbose --profile=p1 --key thedotfile + +[ ! -e ${dt}/folder/b ] && echo "should have been updated" && exit 1 +[ ! -e ${dt}/folder/c ] && echo "should have been updated" && exit 1 + +# Reset +rm ${dt}/folder/b +rmdir ${dt}/folder/c + +# +# Test with command-line flag +# + +# file b / folder c should NOT be copied +echo "[+] test with command-line flag" +cd ${ddpath} | ${bin} update -f -c ${cfg} --verbose --profile=p1 --key thedotfile --ignore-missing + +[ -e ${dt}/folder/b ] && echo "should not have been updated" && exit 1 +[ -e ${dt}/folder/c ] && echo "should not have been updated" && exit 1 + +# +# Test with global option +# + +cat > ${cfg} << _EOF +config: + backup: false + create: true + dotpath: dotfiles + ignore_missing_in_dotdrop: true +dotfiles: + thedotfile: + dst: ${tmpd}/folder + src: folder +profiles: + p1: + dotfiles: + - thedotfile +_EOF + +# file b / folder c should NOT be copied +echo "[+] test global option" +cd ${ddpath} | ${bin} update -f -c ${cfg} --verbose --profile=p1 --key thedotfile + +[ -e ${dt}/folder/b ] && echo "should not have been updated" && exit 1 +[ -e ${dt}/folder/c ] && echo "should not have been updated" && exit 1 + +# +# Test with dotfile option +# + +cat > ${cfg} << _EOF +config: + backup: false + create: true + dotpath: dotfiles +dotfiles: + thedotfile: + dst: ${tmpd}/folder + src: folder + ignore_missing_in_dotdrop: true +profiles: + p1: + dotfiles: + - thedotfile +_EOF +# file b / folder c should NOT be copied +echo "[+] test dotfile option" +cd ${ddpath} | ${bin} update -f -c ${cfg} --verbose --profile=p1 --key thedotfile + +[ -e ${dt}/folder/b ] && echo "should not have been updated" && exit 1 +[ -e ${dt}/folder/c ] && echo "should not have been updated" && exit 1 + +# CLEANING +rm -rf ${tmps} ${tmpd} + +echo "OK" +exit 0 From 369c52e3821988d16453f417d20308da9aaaaacb Mon Sep 17 00:00:00 2001 From: Marcel Robitaille Date: Sat, 6 Feb 2021 22:49:46 -0500 Subject: [PATCH 12/15] Add tests for ignore-missing on compare --- tests-ng/compare-ignore-missing.sh | 158 +++++++++++++++++++++++++++++ 1 file changed, 158 insertions(+) create mode 100755 tests-ng/compare-ignore-missing.sh diff --git a/tests-ng/compare-ignore-missing.sh b/tests-ng/compare-ignore-missing.sh new file mode 100755 index 0000000..61da321 --- /dev/null +++ b/tests-ng/compare-ignore-missing.sh @@ -0,0 +1,158 @@ +#!/usr/bin/env bash +# author: deadc0de6 (https://github.com/deadc0de6) +# Copyright (c) 2017, deadc0de6 +# +# test updates +# 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}")") + +# 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' || mktemp -d` +echo "[+] dotdrop dir: ${basedir}" +echo "[+] dotpath dir: ${basedir}/dotfiles" +dt="${basedir}/dotfiles" +mkdir -p ${dt}/folder +touch ${dt}/folder/a + +# the dotfile to be imported +tmpd=`mktemp -d --suffix='-dotdrop-tests' || mktemp -d` + +# some files +cp -r ${dt}/folder ${tmpd}/ +mkdir -p ${tmpd}/folder +touch ${tmpd}/folder/b +mkdir ${tmpd}/folder/c + +# create the config file +cfg="${basedir}/config.yaml" +cat > ${cfg} << _EOF +config: + backup: false + create: true + dotpath: dotfiles +dotfiles: + thedotfile: + dst: ${tmpd}/folder + src: folder +profiles: + p1: + dotfiles: + - thedotfile +_EOF + +# +# Test with no ignore-missing setting +# + +# Expect diff +echo "[+] test with no ignore-missing setting" +set +e +cd ${ddpath} | ${bin} compare -c ${cfg} --verbose --profile=p1 +[ "$?" = "0" ] && exit 1 +set -e + +# +# Test with command-line flga +# + +# Expect no diff +echo "[+] test with command-line flag" +set +e +cd ${ddpath} | ${bin} compare -c ${cfg} --verbose --profile=p1 --ignore-missing +[ "$?" != "0" ] && exit 1 +set -e + +# +# Test with global option +# + +cat > ${cfg} << _EOF +config: + backup: false + create: true + dotpath: dotfiles + ignore_missing_in_dotdrop: true +dotfiles: + thedotfile: + dst: ${tmpd}/folder + src: folder +profiles: + p1: + dotfiles: + - thedotfile +_EOF + +# Expect no diff +echo "[+] test global option" +set +e +cd ${ddpath} | ${bin} compare -c ${cfg} --verbose --profile=p1 +[ "$?" != "0" ] && exit 1 +set -e + +# +# Test with dotfile option +# + +cat > ${cfg} << _EOF +config: + backup: false + create: true + dotpath: dotfiles +dotfiles: + thedotfile: + dst: ${tmpd}/folder + src: folder + ignore_missing_in_dotdrop: true +profiles: + p1: + dotfiles: + - thedotfile +_EOF + +# Expect no diff +echo "[+] test dotfile option" +set +e +cd ${ddpath} | ${bin} compare -c ${cfg} --verbose --profile=p1 +[ "$?" != "0" ] && exit 1 +set -e + +# CLEANING +rm -rf ${basedir} ${tmpd} + +echo "OK" +exit 0 From 147b8050495fe7f66029b38d1cfd6be75ecf4cbc Mon Sep 17 00:00:00 2001 From: Marcel Robitaille Date: Sat, 6 Feb 2021 22:57:31 -0500 Subject: [PATCH 13/15] Fix args to call to `_ignore` --- dotdrop/updater.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dotdrop/updater.py b/dotdrop/updater.py index 3c0c44e..1f5891e 100644 --- a/dotdrop/updater.py +++ b/dotdrop/updater.py @@ -360,7 +360,7 @@ class Updater: fleft = os.path.join(left, f) fright = os.path.join(right, f) if (ignore_missing_in_dotdrop and not os.path.exists(fright)) or \ - self._ignore([exist, new]): + self._ignore([fleft, fright]): continue if self.dry: self.log.dry('would cp {} {}'.format(fleft, fright)) From 86f6f26a98febcd70a109c4d86782bc8b03c7dc8 Mon Sep 17 00:00:00 2001 From: Marcel Robitaille Date: Mon, 8 Feb 2021 13:29:46 -0500 Subject: [PATCH 14/15] Document ignore-missing --- docs/config-details.md | 18 ++++++++++++++++++ docs/usage.md | 21 ++++++++++++++++++++- 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/docs/config-details.md b/docs/config-details.md index ce26260..a760f0e 100644 --- a/docs/config-details.md +++ b/docs/config-details.md @@ -645,3 +645,21 @@ dotfiles: trans_read: r_echo_var trans_write: w_echo_var ``` + +## Ignoring missing files + +The [ignore missing files setting](./usage/#ignoring-missing-files) +can be configured globally or on a specific dotfile. + +To configure globally, place the following in `config.yaml`: +```yaml +config: + ignore_missing_in_dotdrop: True +``` + +To configure per dotfile: +```yaml +dotfiles: + f_abc: + ignore_missing_in_dotdrop: True +``` diff --git a/docs/usage.md b/docs/usage.md index d550d75..2658c32 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -108,7 +108,8 @@ It is also possible to install all dotfiles for a specific profile in a temporary directory in order to manually compare them with the local version by using `install` and the `-t` switch. -For more options, see the usage with `dotdrop --help` +For more options, see the usage with `dotdrop --help`. +See also [ignoring missing files](#ignoring-missing-files). ## List profiles @@ -218,6 +219,8 @@ Installed to tmp /tmp/dotdrop-6ajz7565 $ diff ~/.vimrc /tmp/dotdrop-6ajz7565/home/user/.vimrc ``` +See also [ignoring missing files](#ignoring-missing-files). + ## Remove dotfiles The command `remove` allows to stop managing a specific dotfile with @@ -287,3 +290,19 @@ export DOTDROP_WORKDIR="/tmp/dotdrop-workdir" ```bash export DOTDROP_WORKERS="10" ``` + +## Ignoring missing files + +Sometimes, it is nice to have [`update`](#update-dotfiles) not copy all the files in the installed directory +or [`compare`](#compare-dotfiles) diff them. + +For example, +maybe you only want to include a single configuration file in your repository +and don't want to include other files the program uses, +such as a cache. +Maybe you only want to change one file and don't want the others cluttering your repository. +Maybe the program changes these files quite often and creates unnecessary diffs in your dotfiles. + +In these cases, you can use the `ingore-missing` option. +This option is available as a flag (`--ignore-missing` or `-z`) to the `update` and `compare` commands, +or [as a configuration option either globally or on a specific dotfile](./config-details/#ignoring-missing-files). From a9e7038d3b70ea83d5e1d1110dfc884dbaf385d0 Mon Sep 17 00:00:00 2001 From: Marcel Robitaille Date: Mon, 8 Feb 2021 13:53:49 -0500 Subject: [PATCH 15/15] Fix broken links? --- docs/config-details.md | 2 +- docs/usage.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/config-details.md b/docs/config-details.md index a760f0e..196b33d 100644 --- a/docs/config-details.md +++ b/docs/config-details.md @@ -648,7 +648,7 @@ dotfiles: ## Ignoring missing files -The [ignore missing files setting](./usage/#ignoring-missing-files) +The [ignore missing files setting](usage.md#ignoring-missing-files) can be configured globally or on a specific dotfile. To configure globally, place the following in `config.yaml`: diff --git a/docs/usage.md b/docs/usage.md index 2658c32..7b2d3fb 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -305,4 +305,4 @@ Maybe the program changes these files quite often and creates unnecessary diffs In these cases, you can use the `ingore-missing` option. This option is available as a flag (`--ignore-missing` or `-z`) to the `update` and `compare` commands, -or [as a configuration option either globally or on a specific dotfile](./config-details/#ignoring-missing-files). +or [as a configuration option either globally or on a specific dotfile](config-details.md#ignoring-missing-files).