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: