1
0
mirror of https://github.com/deadc0de6/dotdrop.git synced 2026-02-12 23:55:20 +00:00

remove un-managed for #403

This commit is contained in:
deadc0de6
2023-09-22 13:52:14 +02:00
parent 235614da9a
commit faf9944a0f
5 changed files with 167 additions and 32 deletions

1
docs/usage.md vendored
View File

@@ -97,6 +97,7 @@ Some available options:
* `-a`/`--force-actions`: Force the execution of actions even if the dotfiles are not installed (see [Fake dotfile and actions](config/config-actions.md#fake-dotfile-and-actions) as an alternative) * `-a`/`--force-actions`: Force the execution of actions even if the dotfiles are not installed (see [Fake dotfile and actions](config/config-actions.md#fake-dotfile-and-actions) as an alternative)
* `-f`/`--force`: Do not ask for any confirmation * `-f`/`--force`: Do not ask for any confirmation
* `-W`/`--workdir-clear`: Clear the `workdir` before installing dotfiles (see [the config entry](config/config-config.md) `clear_workdir`) * `-W`/`--workdir-clear`: Clear the `workdir` before installing dotfiles (see [the config entry](config/config-config.md) `clear_workdir`)
* `-R`/`remove-existing`: Applies to directory dotfiles only (`nolink`) and will remove files not managed by dotdrop in the destination directory
To ignore specific patterns during installation, see [the ignore patterns](config/config-file.md#ignore-patterns). To ignore specific patterns during installation, see [the ignore patterns](config/config-file.md#ignore-patterns).

View File

@@ -720,7 +720,8 @@ def _get_install_installer(opts, tmpdir=None):
totemp=tmpdir, totemp=tmpdir,
showdiff=opts.install_showdiff, showdiff=opts.install_showdiff,
backup_suffix=opts.install_backup_suffix, backup_suffix=opts.install_backup_suffix,
diff_cmd=opts.diff_command) diff_cmd=opts.diff_command,
remove_existing_in_dir=opts.install_remove_existing)
return inst return inst

View File

@@ -28,7 +28,8 @@ class Installer:
def __init__(self, base='.', create=True, backup=True, def __init__(self, base='.', create=True, backup=True,
dry=False, safe=False, workdir='~/.config/dotdrop', dry=False, safe=False, workdir='~/.config/dotdrop',
debug=False, diff=True, totemp=None, showdiff=False, debug=False, diff=True, totemp=None, showdiff=False,
backup_suffix='.dotdropbak', diff_cmd=''): backup_suffix='.dotdropbak', diff_cmd='',
remove_existing_in_dir=False):
""" """
@base: directory path where to search for templates @base: directory path where to search for templates
@create: create directory hierarchy if missing when installing @create: create directory hierarchy if missing when installing
@@ -42,6 +43,8 @@ class Installer:
@showdiff: show the diff before overwriting (or asking for) @showdiff: show the diff before overwriting (or asking for)
@backup_suffix: suffix for dotfile backup file @backup_suffix: suffix for dotfile backup file
@diff_cmd: diff command to use @diff_cmd: diff command to use
@remove_existing_in_dir: remove file in dir dotfiles
if not managed by dotdrop
""" """
self.create = create self.create = create
self.backup = backup self.backup = backup
@@ -60,6 +63,7 @@ class Installer:
self.backup_suffix = backup_suffix self.backup_suffix = backup_suffix
self.diff_cmd = diff_cmd self.diff_cmd = diff_cmd
self.action_executed = False self.action_executed = False
self.remove_existing_in_dir = remove_existing_in_dir
# avoids printing file copied logs # avoids printing file copied logs
# when using install_to_tmp for comparing # when using install_to_tmp for comparing
self.comparing = False self.comparing = False
@@ -130,10 +134,12 @@ class Installer:
if linktype == LinkTypes.NOLINK: if linktype == LinkTypes.NOLINK:
# normal file # normal file
if isdir: if isdir:
ret, err = self._copy_dir(templater, src, dst, ret, err, ins = self._copy_dir(templater, src, dst,
actionexec=actionexec, actionexec=actionexec,
noempty=noempty, ignore=ignore, noempty=noempty, ignore=ignore,
is_template=is_template) is_template=is_template)
if self.remove_existing_in_dir and ins:
self._remove_existing_in_dir(dst, ins)
else: else:
ret, err = self._copy_file(templater, src, dst, ret, err = self._copy_file(templater, src, dst,
actionexec=actionexec, actionexec=actionexec,
@@ -583,10 +589,15 @@ class Installer:
- False, error_msg : error - False, error_msg : error
- False, None : ignored - False, None : ignored
- False, 'aborted' : user aborted - False, 'aborted' : user aborted
third arg returned is the list managed dotfiles
in the destination or an empty list if anything
fails
""" """
self.log.dbg(f'deploy dir {src}') self.log.dbg(f'deploy dir {src}')
# default to nothing installed and no error # default to nothing installed and no error
ret = False, None ret = False
dst_dotfiles = []
# handle all files in dir # handle all files in dir
for entry in os.listdir(src): for entry in os.listdir(src):
@@ -594,35 +605,75 @@ class Installer:
self.log.dbg(f'deploy sub from {dst}: {entry}') self.log.dbg(f'deploy sub from {dst}: {entry}')
if not os.path.isdir(fpath): if not os.path.isdir(fpath):
# is file # is file
fdst = os.path.join(dst, entry)
dst_dotfiles.append(fdst)
res, err = self._copy_file(templater, fpath, res, err = self._copy_file(templater, fpath,
os.path.join(dst, entry), fdst,
actionexec=actionexec, actionexec=actionexec,
noempty=noempty, noempty=noempty,
ignore=ignore, ignore=ignore,
is_template=is_template) is_template=is_template)
if not res and err: if not res and err:
# error occured # error occured
return res, err return res, err, []
if res: if res:
# something got installed # something got installed
ret = True, None
ret = True
else: else:
# is directory # is directory
res, err = self._copy_dir(templater, fpath, dpath = os.path.join(dst, entry)
os.path.join(dst, entry), dst_dotfiles.append(dpath)
actionexec=actionexec, res, err, subs = self._copy_dir(templater, fpath,
noempty=noempty, dpath,
ignore=ignore, actionexec=actionexec,
is_template=is_template) noempty=noempty,
ignore=ignore,
is_template=is_template)
dst_dotfiles.extend(subs)
if not res and err: if not res and err:
# error occured # error occured
return res, err return res, err, []
if res: if res:
# something got installed # something got installed
ret = True, None ret = True
return ret
return ret, None, dst_dotfiles
def _is_path_in(self, path, paths):
"""return true if path is in paths"""
return any(samefile(path, p) for p in paths)
def _remove_existing_in_dir(self, directory, installed_files=None):
"""
with --remove-existing this will remove
any file in managed directory which
are not handled by dotdrop
"""
if not installed_files:
return
if not os.path.exists(directory) or not os.path.isdir(directory):
return
to_remove = []
for root, dirs, files in os.walk(directory):
for name in files:
path = os.path.join(root, name)
if self._is_path_in(path, installed_files):
continue
to_remove.append(os.path.abspath(path))
for name in dirs:
path = os.path.join(root, name)
if self._is_path_in(path, installed_files):
continue
to_remove.append(os.path.abspath(path))
for path in to_remove:
if self.dry:
self.log.dry(f'would remove {path}')
continue
self.log.dbg(f'removing not managed: {path}')
removepath(path, logger=self.log)
@classmethod @classmethod
def _write_content_to_file(cls, content, src, dst): def _write_content_to_file(cls, content, src, dst):

View File

@@ -58,19 +58,19 @@ USAGE = f"""
{BANNER} {BANNER}
Usage: Usage:
dotdrop install [-VbtfndDaW] [-c <path>] [-p <profile>] dotdrop install [-VbtfndDaWR] [-c <path>] [-p <profile>]
[-w <nb>] [<key>...] [-w <nb>] [<key>...]
dotdrop import [-Vbdfm] [-c <path>] [-p <profile>] [-i <pattern>...] dotdrop import [-Vbdfm] [-c <path>] [-p <profile>] [-i <pattern>...]
[--transr=<key>] [--transw=<key>] [--transr=<key>] [--transw=<key>]
[-l <link>] [-s <path>] <path>... [-l <link>] [-s <path>] <path>...
dotdrop compare [-LVbz] [-c <path>] [-p <profile>] dotdrop compare [-LVbz] [-c <path>] [-p <profile>]
[-w <nb>] [-C <file>...] [-i <pattern>...] [-w <nb>] [-C <file>...] [-i <pattern>...]
dotdrop update [-VbfdkPz] [-c <path>] [-p <profile>] dotdrop update [-VbfdkPz] [-c <path>] [-p <profile>]
[-w <nb>] [-i <pattern>...] [<path>...] [-w <nb>] [-i <pattern>...] [<path>...]
dotdrop remove [-Vbfdk] [-c <path>] [-p <profile>] [<path>...] dotdrop remove [-Vbfdk] [-c <path>] [-p <profile>] [<path>...]
dotdrop files [-VbTG] [-c <path>] [-p <profile>] dotdrop files [-VbTG] [-c <path>] [-p <profile>]
dotdrop detail [-Vb] [-c <path>] [-p <profile>] [<key>...] dotdrop detail [-Vb] [-c <path>] [-p <profile>] [<key>...]
dotdrop profiles [-VbG] [-c <path>] dotdrop profiles [-VbG] [-c <path>]
dotdrop --help dotdrop --help
dotdrop --version dotdrop --version
@@ -91,6 +91,7 @@ Options:
-n --nodiff Do not diff when installing. -n --nodiff Do not diff when installing.
-p --profile=<profile> Specify the profile to use [default: {PROFILE}]. -p --profile=<profile> Specify the profile to use [default: {PROFILE}].
-P --show-patch Provide a one-liner to manually patch template. -P --show-patch Provide a one-liner to manually patch template.
-R --remove-existing Remove existing file on install directory.
-s --as=<path> Import as a different path from actual path. -s --as=<path> Import as a different path from actual path.
--transr=<key> Associate trans_read key on import. --transr=<key> Associate trans_read key on import.
--transw=<key> Apply trans_write key on import. --transw=<key> Apply trans_write key on import.
@@ -297,6 +298,7 @@ class Options(AttrMonitor):
self.install_force_chmod = self.force_chmod self.install_force_chmod = self.force_chmod
self.install_clear_workdir = self.args['--workdir-clear'] or \ self.install_clear_workdir = self.args['--workdir-clear'] or \
self.clear_workdir self.clear_workdir
self.install_remove_existing = self.args['--remove-existing']
def _apply_args_compare(self): def _apply_args_compare(self):
"""compare specifics""" """compare specifics"""

80
tests-ng/install-and-remove.sh vendored Executable file
View File

@@ -0,0 +1,80 @@
#!/usr/bin/env bash
# author: deadc0de6 (https://github.com/deadc0de6)
# Copyright (c) 2023, deadc0de6
#
# test install and remove existing file in fs
# returns 1 in case of error
#
## start-cookie
set -euo errtrace pipefail
cur=$(cd "$(dirname "${0}")" && pwd)
ddpath="${cur}/../"
PPATH="{PYTHONPATH:-}"
export PYTHONPATH="${ddpath}:${PPATH}"
altbin="python3 -m dotdrop.dotdrop"
if hash coverage 2>/dev/null; then
mkdir -p coverages/
altbin="coverage run -p --data-file coverages/coverage --source=dotdrop -m dotdrop.dotdrop"
fi
bin="${DT_BIN:-${altbin}}"
# shellcheck source=tests-ng/helpers
source "${cur}"/helpers
echo -e "$(tput setaf 6)==> RUNNING $(basename "${BASH_SOURCE[0]}") <==$(tput sgr0)"
## end-cookie
################################################################
# this is the test
################################################################
# dotdrop directory
basedir=$(mktemp -d --suffix='-dotdrop-tests' || mktemp -d)
mkdir -p "${basedir}"/dotfiles
echo "[+] dotdrop dir: ${basedir}"
echo "[+] dotpath dir: ${basedir}/dotfiles"
tmpd=$(mktemp -d --suffix='-dotdrop-tests' || mktemp -d)
clear_on_exit "${basedir}"
clear_on_exit "${tmpd}"
# create the config file
cfg="${basedir}/config.yaml"
cat > "${cfg}" << _EOF
config:
backup: true
create: true
dotpath: dotfiles
dotfiles:
d_dir:
src: dir
dst: ${tmpd}/dir
profiles:
p1:
dotfiles:
- d_dir
_EOF
# create the file in dotpath
mkdir -p "${basedir}"/dotfiles/dir
echo "content" > "${basedir}"/dotfiles/dir/file
# create the file in fs
mkdir -p "${tmpd}"/dir
echo "content" > "${tmpd}"/dir/existing
echo "[+] install"
cd "${ddpath}" | ${bin} install -c "${cfg}" -f -p p1 --verbose
[ "$?" != "0" ] && exit 1
[ ! -e "${tmpd}"/dir/file ] && echo "d_dir file not installed" && exit 1
[ ! -e "${tmpd}"/dir/existing ] && echo "existing removed" && exit 1
echo "[+] install with remove"
cd "${ddpath}" | ${bin} install --remove-existing -c "${cfg}" -f -p p1 --verbose
[ "$?" != "0" ] && exit 1
[ ! -e "${tmpd}"/dir/file ] && echo "d_dir file not installed" && exit 1
[ -e "${tmpd}"/dir/existing ] && echo "existing not removed" && exit 1
echo "OK"
exit 0