Beispiel #1
0
    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)
Beispiel #2
0
    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
Beispiel #3
0
    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
Beispiel #4
0
    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
Beispiel #5
0
 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
Beispiel #6
0
    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
Beispiel #7
0
 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))
Beispiel #8
0
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
Beispiel #9
0
 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
Beispiel #10
0
    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
Beispiel #11
0
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
Beispiel #12
0
    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
Beispiel #13
0
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
Beispiel #14
0
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
Beispiel #15
0
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
Beispiel #16
0
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
Beispiel #17
0
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
Beispiel #18
0
    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
Beispiel #19
0
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