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

reimplement update

This commit is contained in:
deadc0de6
2018-07-20 23:35:44 +02:00
parent 44115fa7d7
commit f309ff80f8
2 changed files with 219 additions and 48 deletions

View File

@@ -15,6 +15,7 @@ from dotdrop.version import __version__ as VERSION
from dotdrop.logger import Logger from dotdrop.logger import Logger
from dotdrop.templategen import Templategen from dotdrop.templategen import Templategen
from dotdrop.installer import Installer from dotdrop.installer import Installer
from dotdrop.updater import Updater
from dotdrop.dotfile import Dotfile from dotdrop.dotfile import Dotfile
from dotdrop.config import Cfg from dotdrop.config import Cfg
from dotdrop.utils import * from dotdrop.utils import *
@@ -39,7 +40,7 @@ Usage:
dotdrop import [-ldVb] [-c <path>] [-p <profile>] <paths>... dotdrop import [-ldVb] [-c <path>] [-p <profile>] <paths>...
dotdrop compare [-Vb] [-c <path>] [-p <profile>] dotdrop compare [-Vb] [-c <path>] [-p <profile>]
[-o <opts>] [--files=<files>] [-o <opts>] [--files=<files>]
dotdrop update [-fdVb] [-c <path>] <path> dotdrop update [-fdVb] [-c <path>] [-p <profile>] <paths>...
dotdrop listfiles [-Vb] [-c <path>] [-p <profile>] dotdrop listfiles [-Vb] [-c <path>] [-p <profile>]
dotdrop list [-Vb] [-c <path>] dotdrop list [-Vb] [-c <path>]
dotdrop --help dotdrop --help
@@ -212,52 +213,12 @@ def compare(opts, conf, tmp, focus=None):
return ret return ret
def update(opts, conf, path): def update(opts, conf, paths):
"""update the dotfile from path""" """update the dotfile(s) from path(s)"""
if not os.path.lexists(path): updater = Updater(conf, opts['dotpath'], opts['dry'],
LOG.err('\"{}\" does not exist!'.format(path)) opts['safe'], opts['debug'])
return False for path in paths:
home = os.path.expanduser(TILD) updater.update(path, opts['profile'])
path = os.path.expanduser(path)
path = os.path.expandvars(path)
# normalize the path
if path.startswith(home):
path = path.lstrip(home)
path = os.path.join(TILD, path)
dotfiles = conf.get_dotfiles(opts['profile'])
subs = [d for d in dotfiles if d.dst == path]
if not subs:
LOG.err('\"{}\" is not managed!'.format(path))
return False
if len(subs) > 1:
found = ','.join([d.src for d in dotfiles])
LOG.err('multiple dotfiles found: {}'.format(found))
return False
dotfile = subs[0]
src = os.path.join(conf.abs_dotpath(opts['dotpath']), dotfile.src)
if os.path.isfile(src) and \
Templategen.get_marker() in open(src, 'r').read():
LOG.warn('\"{}\" uses template, please update manually'.format(src))
return False
# Handle directory update
src_clean = src
if os.path.isdir(src):
src_clean = os.path.join(src, '..')
if samefile(src_clean, path):
# symlink loop
Log.err('dotfile points to itself: {}'.format(path))
return False
cmd = ['cp', '-R', '-L', os.path.expanduser(path), src_clean]
if opts['dry']:
LOG.dry('would run: {}'.format(' '.join(cmd)))
else:
msg = 'Overwrite \"{}\" with \"{}\"?'.format(src, path)
if opts['safe'] and not LOG.ask(msg):
return False
else:
run(cmd, raw=False, debug=opts['debug'])
LOG.log('\"{}\" updated from \"{}\".'.format(src, path))
return True
def importer(opts, conf, paths): def importer(opts, conf, paths):
@@ -396,7 +357,7 @@ def main():
elif args['update']: elif args['update']:
# update a dotfile # update a dotfile
update(opts, conf, args['<path>']) update(opts, conf, args['<paths>'])
except KeyboardInterrupt: except KeyboardInterrupt:
LOG.err('interrupted') LOG.err('interrupted')

210
dotdrop/updater.py Normal file
View File

@@ -0,0 +1,210 @@
"""
author: deadc0de6 (https://github.com/deadc0de6)
Copyright (c) 2017, deadc0de6
handle the update of dotfiles
"""
import os
import shutil
import filecmp
# local imports
from dotdrop.logger import Logger
from dotdrop.templategen import Templategen
import dotdrop.utils as utils
TILD = '~'
class Updater:
BACKUP_SUFFIX = '.dotdropbak'
def __init__(self, conf, dotpath, dry, safe, debug):
self.home = os.path.expanduser(TILD)
self.conf = conf
self.dotpath = dotpath
self.dry = dry
self.safe = safe
self.debug = debug
self.log = Logger()
def _normalize(self, path):
"""normalize the path to match dotfile"""
path = os.path.expanduser(path)
path = os.path.expandvars(path)
# normalize the path
if path.startswith(self.home):
path = path.lstrip(self.home)
path = os.path.join(TILD, path)
return path
def _get_dotfile(self, path, profile):
"""get the dotfile matching this path"""
dotfiles = self.conf.get_dotfiles(profile)
subs = [d for d in dotfiles if d.dst == path]
if not subs:
self.log.err('\"{}\" is not managed!'.format(path))
return None
if len(subs) > 1:
found = ','.join([d.src for d in dotfiles])
self.log.err('multiple dotfiles found: {}'.format(found))
return None
return subs[0]
def update(self, path, profile):
"""update the dotfile installed on path"""
if not os.path.lexists(path):
self.log.err('\"{}\" does not exist!'.format(path))
return False
left = self._normalize(path)
dotfile = self._get_dotfile(path, profile)
if not dotfile:
return False
if self.debug:
self.log.dbg('updating {} from {}'.format(dotfile, path))
right = os.path.join(self.conf.abs_dotpath(self.dotpath), dotfile.src)
# go through all files and update
if os.path.isdir(path):
return self._handle_dir(left, right)
return self._handle_file(left, right)
def _is_template(self, path):
if Templategen.get_marker() not in open(path, 'r').read():
return False
self.log.warn('{} uses template, update manually'.format(right))
return True
def _handle_file(self, left, right, compare=True):
"""sync left (deployed file) and right (dotdrop dotfile)"""
if self.debug:
self.log.dbg('update for file {} and {}'.format(left, right))
if self._is_template(right):
return False
if compare and filecmp.cmp(left, right, shallow=True):
# no difference
if self.debug:
self.log.dbg('identical files: {} and {}'.format(left, right))
return True
if not self._overwrite(left, right):
return False
try:
if self.dry:
self.log.dry('would cp {} {}'.format(left, right))
else:
if self.debug:
self.log.dbg('cp {} {}'.format(left, right))
shutil.copyfile(left, right)
except IOError as e:
self.log.warn('{} update failed, do manually: {}'.format(left, e))
return False
return True
def _handle_dir(self, left, right):
"""sync left (deployed dir) and right (dotdrop dir)"""
if self.debug:
self.log.dbg('handle update for dir {} to {}'.format(left, right))
# find the difference
diff = filecmp.dircmp(left, right, ignore=None, hide=None)
# handle directories diff
# create dirs that don't exist in dotdrop
if self.debug:
self.log.dbg('handle dirs that do not 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.dry:
self.log.dry('would mkdir -p {}'.format(new))
continue
if self.debug:
self.log.dbg('mkdir -p {}'.format(new))
self._create_dirs(new)
# remove dirs that don't exist in deployed version
if self.debug:
self.log.dbg('remove dirs that do not exist in deployed version')
for toremove in diff.right_only:
new = os.path.join(right, toremove)
if self.dry:
self.log.dry('would rm -r {}'.format(new))
continue
if self.debug:
self.log.dbg('rm -r {}'.format(new))
utils.remove(new)
# handle files diff
# sync files that exist in both but are different
if self.debug:
self.log.dbg('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.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
if self.debug:
self.log.dbg('copy files not existing 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.dry:
self.log.dry('would cp {} {}'.format(exist, new))
continue
if self.debug:
self.log.dbg('cp {} {}'.format(exist, new))
shutil.copyfile(exist, new)
# remove files that don't exist in deployed version
if self.debug:
self.log.dbg('remove files that do not 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.dry:
self.log.dry('would rm {}'.format(new))
continue
if self.debug:
self.log.dbg('rm {}'.format(new))
utils.remove(new)
return True
def _create_dirs(self, directory):
"""mkdir -p <directory>"""
if os.path.exists(directory):
return True
if self.dry:
self.log.dry('would mkdir -p {}'.format(directory))
return True
if self.debug:
self.log.dbg('mkdir -p {}'.format(directory))
os.makedirs(directory)
return os.path.exists(directory)
def _overwrite(self, src, dst):
"""ask for overwritting"""
msg = 'Overwrite \"{}\" with \"{}\"?'.format(dst, src)
if self.safe and not self.log.ask(msg):
return False
return True