def _merge_dirs_remove_right_only(self, diff, left, right, ignore_missing_in_dotdrop): """remove dirs that don't exist in deployed version""" for toremove in diff.right_only: old = os.path.join(right, toremove) if not os.path.isdir(old): # ignore files for now continue if self._ignore([old]): continue if self.dry: self.log.dry('would rm -r {}'.format(old)) continue self.log.dbg('rm -r {}'.format(old)) if not self._confirm_rm_r(old): continue removepath(old, logger=self.log) self.log.sub('\"{}\" dir removed'.format(old)) # handle files diff # sync files that exist in both but are different fdiff = diff.diff_files fdiff.extend(diff.funny_files) fdiff.extend(diff.common_funny) for file in fdiff: fleft = os.path.join(left, file) fright = os.path.join(right, file) if (ignore_missing_in_dotdrop and not os.path.exists(fright)) or \ self._ignore([fleft, fright]): continue if self.dry: self.log.dry('would cp {} {}'.format(fleft, fright)) continue self.log.dbg('cp {} {}'.format(fleft, fright)) self._handle_file(fleft, fright, compare=False)
def _update(self, path, dotfile): """update dotfile from file pointed by path""" ret = False new_path = None ignores = list(set(self.ignore + dotfile.upignore)) self.ignores = patch_ignores(ignores, dotfile.dst, debug=self.debug) 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) if self._ignore([path, dtpath]): self.log.sub('\"{}\" ignored'.format(dotfile.key)) return True # apply write transformation if any new_path = self._apply_trans_w(path, dotfile) if not new_path: return False if os.path.isdir(new_path): ret = self._handle_dir(new_path, dtpath) else: ret = self._handle_file(new_path, dtpath) # clean temporary files if new_path != path and os.path.exists(new_path): removepath(new_path, logger=self.log) return ret
def _update(self, path, dotfile): """update dotfile from file pointed by path""" ret = False new_path = None ignores = list(set(self.ignore + dotfile.upignore)) self.ignores = patch_ignores(ignores, dotfile.dst, debug=self.debug) if self.debug: self.log.dbg('ignore pattern(s): {}'.format(self.ignores)) 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(deployed_path): msg = '\"{}\" does not exist' self.log.err(msg.format(deployed_path)) return False if not os.path.exists(local_path): msg = '\"{}\" does not exist, import it first' self.log.err(msg.format(local_path)) return False 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]): self.log.sub('\"{}\" ignored'.format(dotfile.key)) return True # apply write transformation if any new_path = self._apply_trans_w(deployed_path, dotfile) if not new_path: return False # save current rights 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, local_path, dotfile) else: ret = self._handle_file(new_path, local_path, dotfile) if deployed_mode != local_mode: # mirror rights if self.debug: m = 'adopt mode {:o} for {}' 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 != deployed_path and os.path.exists(new_path): removepath(new_path, logger=self.log) return ret
def _link(self, src, dst, actionexec=None): """ set src as a link target of dst return - True, None: success - False, error_msg: error - False, None, ignored """ overwrite = not self.safe if os.path.lexists(dst): if os.path.realpath(dst) == os.path.realpath(src): msg = 'ignoring "{}", link already exists'.format(dst) if self.debug: self.log.dbg(msg) return False, None if self.dry: self.log.dry('would remove {} and link to {}'.format(dst, src)) return True, None if self.showdiff: self._diff_before_write(src, dst, quiet=False) msg = 'Remove "{}" for link creation?'.format(dst) if self.safe and not self.log.ask(msg): err = 'ignoring "{}", link was not created'.format(dst) return False, err overwrite = True try: utils.removepath(dst) except OSError as e: err = 'something went wrong with {}: {}'.format(src, e) return False, err if self.dry: self.log.dry('would link {} to {}'.format(dst, src)) return True, None base = os.path.dirname(dst) if not self._create_dirs(base): err = 'error creating directory for {}'.format(dst) return False, err r, e = self._exec_pre_actions(actionexec) if not r: return False, e # re-check in case action created the file if os.path.lexists(dst): msg = 'Remove "{}" for link creation?'.format(dst) if self.safe and not overwrite and not self.log.ask(msg): err = 'ignoring "{}", link was not created'.format(dst) return False, err try: utils.removepath(dst) except OSError as e: err = 'something went wrong with {}: {}'.format(src, e) return False, err os.symlink(src, dst) self.log.sub('linked {} to {}'.format(dst, src)) return True, None
def _is_different(self, src, dst, content=None): """ returns True if file is different and needs to be installed """ # check file content tmp = None if content: tmp = utils.write_to_tmpfile(content) src = tmp ret = utils.fastdiff(src, dst) if ret: self.log.dbg('content differ') if content: utils.removepath(tmp) return ret
def _show_diff_before_write(self, src, dst, content=None): """ diff before writing using a temp file if content is not None returns diff string ('' if same) """ tmp = None if content: tmp = utils.write_to_tmpfile(content) src = tmp diff = utils.diff(modified=src, original=dst, diff_cmd=self.diff_cmd) if tmp: utils.removepath(tmp, logger=self.log) if diff: self._print_diff(src, dst, diff) return diff
def _merge_dirs_remove_right_only_2(self, diff, right): """remove files that don't exist in deployed version""" for toremove in diff.right_only: new = os.path.join(right, toremove) if not os.path.exists(new): continue if os.path.isdir(new): # ignore dirs, done above continue if self._ignore([new]): continue if self.dry: self.log.dry('would rm {}'.format(new)) continue self.log.dbg('rm {}'.format(new)) removepath(new, logger=self.log) self.log.sub('\"{}\" removed'.format(new))
def apply_trans(dotpath, dotfile, templater, debug=False): """ apply the read transformation to the dotfile return None if fails and new source if succeed """ src = dotfile.src new_src = '{}.{}'.format(src, TRANS_SUFFIX) trans = dotfile.trans_r LOG.dbg('executing transformation: {}'.format(trans)) srcpath = os.path.join(dotpath, src) temp = os.path.join(dotpath, new_src) if not trans.transform(srcpath, temp, templater=templater, debug=debug): msg = 'transformation \"{}\" failed for {}' LOG.err(msg.format(trans.key, dotfile.key)) if new_src and os.path.exists(new_src): removepath(new_src, LOG) return None return new_src
def _apply_trans_w(self, path, dotfile): """apply write transformation to dotfile""" trans = dotfile.get_trans_w() if not trans: return path self.log.dbg('executing write transformation {}'.format(trans)) tmp = get_unique_tmp_name() self.templater.restore_vars(self.tvars) newvars = dotfile.get_dotfile_variables() self.templater.add_tmp_vars(newvars=newvars) if not trans.transform(path, tmp, templater=self.templater, debug=self.debug): msg = 'transformation \"{}\" failed for {}' self.log.err(msg.format(trans.key, dotfile.key)) if os.path.exists(tmp): removepath(tmp, logger=self.log) return None return tmp
def _update(self, path, dotfile): """update dotfile from file pointed by path""" ret = False new_path = None ignores = list(set(self.ignore + dotfile.upignore)) self.ignores = patch_ignores(ignores, dotfile.dst, debug=self.debug) 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) if self._ignore([path, dtpath]): self.log.sub('\"{}\" ignored'.format(dotfile.key)) return True # apply write transformation if any new_path = self._apply_trans_w(path, dotfile) if not new_path: return False # save current rights fsmode = get_file_perm(path) dfmode = get_file_perm(dtpath) # handle the pointed file if os.path.isdir(new_path): ret = self._handle_dir(new_path, dtpath) else: ret = self._handle_file(new_path, dtpath) if fsmode != dfmode: # 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) if r: ret = True # clean temporary files if new_path != path and os.path.exists(new_path): removepath(new_path, logger=self.log) return ret
def _dotfile_compare(o, dotfile, tmp): """ compare a dotfile returns True if same """ t = _get_templater(o) 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) # add dotfile variables newvars = dotfile.get_dotfile_variables() t.add_tmp_vars(newvars=newvars) # dotfiles does not exist / not installed if o.debug: LOG.dbg('comparing {}'.format(dotfile)) src = dotfile.src if not os.path.lexists(os.path.expanduser(dotfile.dst)): line = '=> compare {}: \"{}\" does not exist on destination' LOG.log(line.format(dotfile.key, dotfile.dst)) return False # apply transformation tmpsrc = None if dotfile.trans_r: if o.debug: LOG.dbg('applying transformation before comparing') tmpsrc = apply_trans(o.dotpath, dotfile, t, debug=o.debug) if not tmpsrc: # could not apply trans return False src = tmpsrc # is a symlink pointing to itself asrc = os.path.join(o.dotpath, os.path.expanduser(src)) adst = os.path.expanduser(dotfile.dst) if os.path.samefile(asrc, adst): if o.debug: line = '=> compare {}: diffing with \"{}\"' LOG.dbg(line.format(dotfile.key, dotfile.dst)) LOG.dbg('points to itself') return True ignores = list(set(o.compare_ignore + dotfile.cmpignore)) ignores = patch_ignores(ignores, dotfile.dst, debug=o.debug) insttmp = None if dotfile.template and Templategen.is_template(src, ignore=ignores): # install dotfile to temporary dir for compare ret, err, insttmp = inst.install_to_temp(t, tmp, src, dotfile.dst, is_template=True, chmod=dotfile.chmod) if not ret: # failed to install to tmp line = '=> compare {} error: {}' LOG.log(line.format(dotfile.key, err)) LOG.err(err) return False src = insttmp # compare diff = comp.compare(src, dotfile.dst, ignore=ignores) # clean tmp transformed dotfile if any if tmpsrc: tmpsrc = os.path.join(o.dotpath, tmpsrc) if os.path.exists(tmpsrc): removepath(tmpsrc, LOG) # clean tmp template dotfile if any if insttmp: if os.path.exists(insttmp): removepath(insttmp, LOG) if diff != '': # print diff results line = '=> compare {}: diffing with \"{}\"' LOG.log(line.format(dotfile.key, dotfile.dst)) if o.compare_fileonly: LOG.raw('<files are different>') else: LOG.emph(diff) return False # no difference if o.debug: line = '=> compare {}: diffing with \"{}\"' LOG.dbg(line.format(dotfile.key, dotfile.dst)) LOG.dbg('same file') return True
def _symlink(self, src, dst, actionexec=None): """ set src as a link target of dst return - True, None : success - False, error_msg : error - False, None : ignored - False, 'aborted' : user aborted """ overwrite = not self.safe if os.path.lexists(dst): # symlink exists if os.path.realpath(dst) == os.path.realpath(src): msg = 'ignoring "{}", link already exists'.format(dst) self.log.dbg(msg) return False, None if self.dry: self.log.dry('would remove {} and link to {}'.format(dst, src)) return True, None if self.showdiff: self._show_diff_before_write(src, dst) msg = 'Remove "{}" for link creation?'.format(dst) if self.safe and not self.log.ask(msg): return False, 'aborted' # remove symlink overwrite = True try: utils.removepath(dst) except OSError as exc: err = 'something went wrong with {}: {}'.format(src, exc) return False, err if self.dry: self.log.dry('would link {} to {}'.format(dst, src)) return True, None base = os.path.dirname(dst) if not self._create_dirs(base): err = 'error creating directory for {}'.format(dst) return False, err # execute pre-actions ret, err = self._exec_pre_actions(actionexec) if not ret: return False, err # re-check in case action created the file if os.path.lexists(dst): msg = 'Remove "{}" for link creation?'.format(dst) if self.safe and not overwrite and not self.log.ask(msg): return False, 'aborted' try: utils.removepath(dst) except OSError as exc: err = 'something went wrong with {}: {}'.format(src, exc) return False, err # create symlink os.symlink(src, dst) if not self.comparing: self.log.sub('linked {} to {}'.format(dst, src)) return True, None
def cmd_remove(o): """remove dotfile from dotpath and from config""" paths = o.remove_path iskey = o.remove_iskey if not paths: LOG.log('no dotfile to remove') return False if o.debug: LOG.dbg('dotfile(s) to remove: {}'.format(','.join(paths))) removed = [] for key in paths: if not iskey: # by path dotfiles = o.conf.get_dotfile_by_dst(key) if not dotfiles: LOG.warn('{} ignored, does not exist'.format(key)) continue else: # by key dotfile = o.conf.get_dotfile(key) if not dotfile: LOG.warn('{} ignored, does not exist'.format(key)) continue dotfiles = [dotfile] for dotfile in dotfiles: k = dotfile.key # ignore if uses any type of link if dotfile.link != LinkTypes.NOLINK: msg = '{} uses link/link_children, remove manually' LOG.warn(msg.format(k)) continue if o.debug: LOG.dbg('removing {}'.format(key)) # make sure is part of the profile if dotfile.key not in [d.key for d in o.dotfiles]: msg = '{} ignored, not associated to this profile' LOG.warn(msg.format(key)) continue profiles = o.conf.get_profiles_by_dotfile_key(k) pkeys = ','.join([p.key for p in profiles]) if o.dry: LOG.dry('would remove {} from {}'.format(dotfile, pkeys)) continue msg = 'Remove \"{}\" from all these profiles: {}'.format(k, pkeys) if o.safe and not LOG.ask(msg): return False if o.debug: LOG.dbg('remove dotfile: {}'.format(dotfile)) for profile in profiles: if not o.conf.del_dotfile_from_profile(dotfile, profile): return False if not o.conf.del_dotfile(dotfile): return False # remove dotfile from dotpath dtpath = os.path.join(o.dotpath, dotfile.src) removepath(dtpath, LOG) # remove empty directory parent = os.path.dirname(dtpath) # remove any empty parent up to dotpath while parent != o.dotpath: if os.path.isdir(parent) and not os.listdir(parent): msg = 'Remove empty dir \"{}\"'.format(parent) if o.safe and not LOG.ask(msg): break removepath(parent, LOG) parent = os.path.dirname(parent) removed.append(dotfile.key) if o.dry: LOG.dry('new config file would be:') LOG.raw(o.conf.dump()) else: o.conf.save() if removed: LOG.log('\ndotfile(s) removed: {}'.format(','.join(removed))) else: LOG.log('\nno dotfile removed') return True
def main(): """entry point""" # check dependencies are met try: dependencies_met() except Exception as e: LOG.err(e) return False t0 = time.time() try: o = Options() except YamlException as e: LOG.err('config error: {}'.format(str(e))) return False except UndefinedException as e: LOG.err('config error: {}'.format(str(e))) return False if o.debug: LOG.dbg('\n\n') options_time = time.time() - t0 ret = True t0 = time.time() command = '' try: if o.cmd_profiles: # list existing profiles command = 'profiles' if o.debug: LOG.dbg('running cmd: {}'.format(command)) cmd_list_profiles(o) elif o.cmd_files: # list files for selected profile command = 'files' if o.debug: LOG.dbg('running cmd: {}'.format(command)) cmd_files(o) elif o.cmd_install: # install the dotfiles stored in dotdrop command = 'install' if o.debug: LOG.dbg('running cmd: {}'.format(command)) ret = cmd_install(o) elif o.cmd_compare: # compare local dotfiles with dotfiles stored in dotdrop command = 'compare' if o.debug: LOG.dbg('running cmd: {}'.format(command)) tmp = get_tmpdir() ret = cmd_compare(o, tmp) # clean tmp directory removepath(tmp, LOG) elif o.cmd_import: # import dotfile(s) command = 'import' if o.debug: LOG.dbg('running cmd: {}'.format(command)) ret = cmd_importer(o) elif o.cmd_update: # update a dotfile command = 'update' if o.debug: LOG.dbg('running cmd: {}'.format(command)) ret = cmd_update(o) elif o.cmd_detail: # detail files command = 'detail' if o.debug: LOG.dbg('running cmd: {}'.format(command)) cmd_detail(o) elif o.cmd_remove: # remove dotfile command = 'remove' if o.debug: LOG.dbg('running cmd: {}'.format(command)) cmd_remove(o) except KeyboardInterrupt: LOG.err('interrupted') ret = False cmd_time = time.time() - t0 if o.debug: LOG.dbg('done executing command \"{}\"'.format(command)) LOG.dbg('options loaded in {}'.format(options_time)) LOG.dbg('command executed in {}'.format(cmd_time)) if ret and o.conf.save(): LOG.log('config file updated') if o.debug: LOG.dbg('return {}'.format(ret)) return ret
def _dotfile_install(o, dotfile, tmpdir=None): """ install a dotfile returns <success, dotfile key, err> """ # installer inst = _get_install_installer(o, tmpdir=tmpdir) # templater t = _get_templater(o) # add dotfile variables newvars = dotfile.get_dotfile_variables() t.add_tmp_vars(newvars=newvars) preactions = [] if not o.install_temporary: preactions.extend(dotfile.get_pre_actions()) defactions = o.install_default_actions_pre pre_actions_exec = action_executor(o, preactions, defactions, t, post=False) if o.debug: LOG.dbg('installing dotfile: \"{}\"'.format(dotfile.key)) LOG.dbg(dotfile.prt()) ignores = list(set(o.install_ignore + dotfile.instignore)) ignores = patch_ignores(ignores, dotfile.dst, debug=o.debug) is_template = dotfile.template and Templategen.is_template( dotfile.src, ignore=ignores, ) if hasattr(dotfile, 'link') and dotfile.link == LinkTypes.LINK: # link r, err = inst.install(t, dotfile.src, dotfile.dst, dotfile.link, actionexec=pre_actions_exec, is_template=is_template, ignore=ignores, chmod=dotfile.chmod) elif hasattr(dotfile, 'link') and \ dotfile.link == LinkTypes.LINK_CHILDREN: # link_children r, err = inst.install(t, dotfile.src, dotfile.dst, dotfile.link, actionexec=pre_actions_exec, is_template=is_template, chmod=dotfile.chmod, ignore=ignores) else: # nolink src = dotfile.src tmp = None if dotfile.trans_r: tmp = apply_trans(o.dotpath, dotfile, t, debug=o.debug) if not tmp: return False, dotfile.key, None src = tmp r, err = inst.install(t, src, dotfile.dst, LinkTypes.NOLINK, actionexec=pre_actions_exec, noempty=dotfile.noempty, ignore=ignores, is_template=is_template, chmod=dotfile.chmod) if tmp: tmp = os.path.join(o.dotpath, tmp) if os.path.exists(tmp): removepath(tmp, LOG) # check result of installation if r: # dotfile was installed if not o.install_temporary: defactions = o.install_default_actions_post postactions = dotfile.get_post_actions() post_actions_exec = action_executor(o, postactions, defactions, t, post=True) post_actions_exec() else: # dotfile was NOT installed if o.install_force_action: # pre-actions if o.debug: LOG.dbg('force pre action execution ...') pre_actions_exec() # post-actions if o.debug: LOG.dbg('force post action execution ...') defactions = o.install_default_actions_post postactions = dotfile.get_post_actions() post_actions_exec = action_executor(o, postactions, defactions, t, post=True) post_actions_exec() return r, dotfile.key, err
def cmd_compare(o, tmp): """compare dotfiles and return True if all identical""" dotfiles = o.dotfiles if not dotfiles: msg = 'no dotfile defined for this profile (\"{}\")' LOG.warn(msg.format(o.profile)) return True # compare only specific files same = True selected = dotfiles if o.compare_focus: selected = _select(o.compare_focus, dotfiles) if len(selected) < 1: return False t = _get_templater(o) tvars = t.add_tmp_vars() 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) for dotfile in selected: if not dotfile.src and not dotfile.dst: # ignore fake dotfile continue # add dotfile variables t.restore_vars(tvars) newvars = dotfile.get_dotfile_variables() t.add_tmp_vars(newvars=newvars) # dotfiles does not exist / not installed if o.debug: LOG.dbg('comparing {}'.format(dotfile)) src = dotfile.src if not os.path.lexists(os.path.expanduser(dotfile.dst)): line = '=> compare {}: \"{}\" does not exist on destination' LOG.log(line.format(dotfile.key, dotfile.dst)) same = False continue # apply transformation tmpsrc = None if dotfile.trans_r: if o.debug: LOG.dbg('applying transformation before comparing') tmpsrc = apply_trans(o.dotpath, dotfile, t, debug=o.debug) if not tmpsrc: # could not apply trans same = False continue src = tmpsrc # is a symlink pointing to itself asrc = os.path.join(o.dotpath, os.path.expanduser(src)) adst = os.path.expanduser(dotfile.dst) if os.path.samefile(asrc, adst): if o.debug: line = '=> compare {}: diffing with \"{}\"' LOG.dbg(line.format(dotfile.key, dotfile.dst)) LOG.dbg('points to itself') continue # install dotfile to temporary dir and compare ret, err, insttmp = inst.install_to_temp(t, tmp, src, dotfile.dst, template=dotfile.template) if not ret: # failed to install to tmp line = '=> compare {}: error' LOG.log(line.format(dotfile.key, err)) LOG.err(err) same = False continue ignores = list(set(o.compare_ignore + dotfile.cmpignore)) ignores = patch_ignores(ignores, dotfile.dst, debug=o.debug) diff = comp.compare(insttmp, dotfile.dst, ignore=ignores) # clean tmp transformed dotfile if any if tmpsrc: tmpsrc = os.path.join(o.dotpath, tmpsrc) if os.path.exists(tmpsrc): removepath(tmpsrc, LOG) if diff == '': # no difference if o.debug: line = '=> compare {}: diffing with \"{}\"' LOG.dbg(line.format(dotfile.key, dotfile.dst)) LOG.dbg('same file') else: # print diff results line = '=> compare {}: diffing with \"{}\"' LOG.log(line.format(dotfile.key, dotfile.dst)) if o.compare_fileonly: LOG.raw('<files are different>') else: LOG.emph(diff) same = False return same
def _exec_command(opts): """execute command""" ret = True command = '' try: if opts.cmd_profiles: # list existing profiles command = 'profiles' LOG.dbg('running cmd: {}'.format(command)) cmd_list_profiles(opts) elif opts.cmd_files: # list files for selected profile command = 'files' LOG.dbg('running cmd: {}'.format(command)) cmd_files(opts) elif opts.cmd_install: # install the dotfiles stored in dotdrop command = 'install' LOG.dbg('running cmd: {}'.format(command)) ret = cmd_install(opts) elif opts.cmd_compare: # compare local dotfiles with dotfiles stored in dotdrop command = 'compare' LOG.dbg('running cmd: {}'.format(command)) tmp = get_tmpdir() ret = cmd_compare(opts, tmp) # clean tmp directory removepath(tmp, LOG) elif opts.cmd_import: # import dotfile(s) command = 'import' LOG.dbg('running cmd: {}'.format(command)) ret = cmd_importer(opts) elif opts.cmd_update: # update a dotfile command = 'update' LOG.dbg('running cmd: {}'.format(command)) ret = cmd_update(opts) elif opts.cmd_detail: # detail files command = 'detail' LOG.dbg('running cmd: {}'.format(command)) cmd_detail(opts) elif opts.cmd_remove: # remove dotfile command = 'remove' LOG.dbg('running cmd: {}'.format(command)) cmd_remove(opts) except KeyboardInterrupt: LOG.err('interrupted') ret = False return ret, command
def _merge_dirs(self, diff): """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]): return True # create dirs that don't exist in dotdrop for toadd in diff.left_only: exist = os.path.join(left, toadd) if not os.path.isdir(exist): # ignore files for now continue # match to dotdrop dotpath new = os.path.join(right, toadd) if self._ignore([exist, new]): self.log.sub('\"{}\" ignored'.format(exist)) continue if self.dry: self.log.dry('would cp -r {} {}'.format(exist, new)) continue if self.debug: self.log.dbg('cp -r {} {}'.format(exist, new)) # Newly created directory should be copied as is (for efficiency). def ig(src, names): whitelist, blacklist = set(), set() for ignore in self.ignores: for name in names: path = os.path.join(src, name) if ignore.startswith('!') and \ fnmatch.fnmatch(path, ignore[1:]): # add to whitelist whitelist.add(name) elif fnmatch.fnmatch(path, ignore): # add to blacklist blacklist.add(name) return blacklist - whitelist shutil.copytree(exist, new, ignore=ig) self.log.sub('\"{}\" dir added'.format(new)) # remove dirs that don't exist in deployed version for toremove in diff.right_only: old = os.path.join(right, toremove) if not os.path.isdir(old): # ignore files for now continue if self._ignore([old]): continue if self.dry: self.log.dry('would rm -r {}'.format(old)) continue if self.debug: self.log.dbg('rm -r {}'.format(old)) if not self._confirm_rm_r(old): continue removepath(old, logger=self.log) self.log.sub('\"{}\" dir removed'.format(old)) # handle files diff # sync files that exist in both but are different fdiff = diff.diff_files fdiff.extend(diff.funny_files) fdiff.extend(diff.common_funny) for f in fdiff: fleft = os.path.join(left, f) fright = os.path.join(right, f) if self._ignore([fleft, fright]): 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) # copy files that don't exist in dotdrop for toadd in diff.left_only: exist = os.path.join(left, toadd) if os.path.isdir(exist): # ignore dirs, done above continue new = os.path.join(right, toadd) if self._ignore([exist, new]): continue if self.dry: self.log.dry('would cp {} {}'.format(exist, new)) continue if self.debug: self.log.dbg('cp {} {}'.format(exist, new)) shutil.copyfile(exist, new) self._mirror_rights(exist, new) self.log.sub('\"{}\" added'.format(new)) # remove files that don't exist in deployed version for toremove in diff.right_only: new = os.path.join(right, toremove) if not os.path.exists(new): continue if os.path.isdir(new): # ignore dirs, done above continue if self._ignore([new]): continue if self.dry: self.log.dry('would rm {}'.format(new)) continue if self.debug: self.log.dbg('rm {}'.format(new)) removepath(new, logger=self.log) self.log.sub('\"{}\" removed'.format(new)) # compare rights for common in diff.common_files: leftf = os.path.join(left, common) rightf = os.path.join(right, common) if not self._same_rights(leftf, rightf): self._mirror_rights(leftf, rightf) # Recursively decent into common subdirectories. for subdir in diff.subdirs.values(): self._merge_dirs(subdir) # Nothing more to do here. return True
def cmd_install(opts): """install dotfiles for this profile""" dotfiles = opts.dotfiles prof = opts.conf.get_profile() adapt_workers(opts, LOG) pro_pre_actions = prof.get_pre_actions() if prof else [] pro_post_actions = prof.get_post_actions() if prof else [] if opts.install_keys: # filtered dotfiles to install uniq = uniq_list(opts.install_keys) dotfiles = [d for d in dotfiles if d.key in uniq] if not dotfiles: msg = 'no dotfile to install for this profile (\"{}\")' LOG.warn(msg.format(opts.profile)) return False LOG.dbg('dotfiles registered for install: {}'.format( [k.key for k in dotfiles])) # the installer tmpdir = None if opts.install_temporary: tmpdir = get_tmpdir() installed = [] # clear the workdir if opts.install_clear_workdir and not opts.dry: LOG.dbg('clearing the workdir under {}'.format(opts.workdir)) for root, _, files in os.walk(opts.workdir): for file in files: fpath = os.path.join(root, file) removepath(fpath, logger=LOG) # execute profile pre-action LOG.dbg('run {} profile pre actions'.format(len(pro_pre_actions))) templ = _get_templater(opts) ret, _ = action_executor(opts, pro_pre_actions, [], templ, post=False)() if not ret: return False # install each dotfile if opts.workers > 1: # in parallel LOG.dbg('run with {} workers'.format(opts.workers)) ex = futures.ThreadPoolExecutor(max_workers=opts.workers) wait_for = [] for dotfile in dotfiles: j = ex.submit(_dotfile_install, opts, dotfile, tmpdir=tmpdir) wait_for.append(j) # check result for fut in futures.as_completed(wait_for): tmpret, key, err = fut.result() # check result if tmpret: installed.append(key) elif err: LOG.err('installing \"{}\" failed: {}'.format(key, err)) else: # sequentially for dotfile in dotfiles: tmpret, key, err = _dotfile_install(opts, dotfile, tmpdir=tmpdir) # check result if tmpret: installed.append(key) elif err: LOG.err('installing \"{}\" failed: {}'.format(key, err)) # execute profile post-action if len(installed) > 0 or opts.install_force_action: msg = 'run {} profile post actions' LOG.dbg(msg.format(len(pro_post_actions))) ret, _ = action_executor(opts, pro_post_actions, [], templ, post=False)() if not ret: return False LOG.dbg('install done: installed \"{}\"'.format(','.join(installed))) if opts.install_temporary: LOG.log('\ninstalled to tmp \"{}\".'.format(tmpdir)) LOG.log('\n{} dotfile(s) installed.'.format(len(installed))) return True