Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bug388 2 #402

Merged
merged 45 commits into from
Sep 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
e86eeae
refactoring
deadc0de6 May 2, 2023
5c16171
add test for #388
deadc0de6 May 2, 2023
49461aa
fix test
deadc0de6 May 4, 2023
53efc8f
logging
deadc0de6 May 4, 2023
96e0ae2
refactoring
deadc0de6 May 4, 2023
0fd2e99
refactoring
deadc0de6 May 4, 2023
b06f31f
fix test
deadc0de6 May 4, 2023
19813b6
ignore pattern doc
deadc0de6 May 4, 2023
8486cda
more tests
deadc0de6 May 4, 2023
80c4e62
more tests
deadc0de6 May 4, 2023
ec715e3
do not shadow negative ignore pattern
deadc0de6 May 4, 2023
186e235
verbosity
deadc0de6 May 9, 2023
8360b24
more logs
deadc0de6 May 15, 2023
d35d106
remove ignore from templategen
deadc0de6 May 15, 2023
e606c9b
refactoring
deadc0de6 May 4, 2023
27d1f71
remove ignore from templategen
deadc0de6 May 15, 2023
a293359
refactoring
deadc0de6 Jul 30, 2023
fa5fb0a
logs
deadc0de6 Jul 30, 2023
abe57c2
more checks
deadc0de6 Jul 30, 2023
0ff1b3e
fix ignores
deadc0de6 Jul 30, 2023
6a4fcb1
prefixes for ignore
deadc0de6 Jul 30, 2023
fd14d0c
verbosity
deadc0de6 Jul 30, 2023
af323cb
linting
deadc0de6 Aug 1, 2023
d1bea2a
force
deadc0de6 Aug 3, 2023
c20fb3a
doc
deadc0de6 Aug 7, 2023
a15fcee
test launcher
deadc0de6 Aug 7, 2023
a42a58f
tests
deadc0de6 Aug 7, 2023
22eee5b
chmod printing
deadc0de6 Aug 7, 2023
ed520de
refactor ignore
deadc0de6 Aug 7, 2023
e91800d
test launcher
deadc0de6 Aug 7, 2023
e48ed8f
tests
deadc0de6 Aug 7, 2023
74357fd
linting
deadc0de6 Aug 9, 2023
4a311e0
fix test
deadc0de6 Aug 9, 2023
0965643
fix spinner
deadc0de6 Aug 9, 2023
b6170c2
ignore special file
deadc0de6 Aug 9, 2023
98f9b2b
fail on TODO/FIXME
deadc0de6 Aug 9, 2023
2a56f32
linting
deadc0de6 Aug 9, 2023
b639be0
tests
deadc0de6 Aug 9, 2023
0e02e88
tests
deadc0de6 Aug 9, 2023
d9b0f3b
error in tests
deadc0de6 Aug 9, 2023
72b9c41
logging
deadc0de6 Aug 9, 2023
a862873
coverage
deadc0de6 Aug 9, 2023
464dc8d
fix coverage combine
deadc0de6 Aug 9, 2023
6298e98
update doc
deadc0de6 Aug 10, 2023
6ec4e35
fix tests
deadc0de6 Sep 7, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ venv
.coverage
.coverage*
htmlcov
coverages/

# IDE
.idea/
Expand Down
37 changes: 37 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ Contents
* [Precedence](#precedence)
* [Variable resolution](#variable-resolution)
* [Rules](#rules)
* [Ignore pattern](#ignore-pattern)
* [Testing](#testing)
* [Testing with unittest](#testing-with-unittest)
* [Testing with bash scripts](#testing-with-bash-scripts)
Expand Down Expand Up @@ -158,6 +159,42 @@ dynvariables:
dvar0: "echo {{@@ var0 @@}}"
```

## Ignore pattern

**compare**

* for files, match with ignore directly
* uses `filecmp.dircmp` to compare directories
* will then match each file that is different
within the directory against the ignore patterns
before printing
* patterns are matched against both files
(in dotpath and in filesystem)

**import**

* for files, match with ignore directly
* uses `shutil.copytree` with a callback
that will match each path against the ignore pattern
* the pattern (and negative pattern) will be matched
against the path(s) that are being imported

**install**

* recursively process each files and
match against the ignore pattern
* patterns are matched against both files
(in dotpath and in filesystem)

**update**

* for files, match with ignore directly
* uses `filecmp.dircmp` to compare directories
* will then match each file that is different
within the directory against the ignore patterns
before printing
* patterns are matched against both files
(in dotpath and in filesystem)

# Testing

Expand Down
11 changes: 11 additions & 0 deletions docs/config/config-file.md
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,17 @@ provided they *would have* been ignored by an earlier ignore pattern (dotdrop wi
case). This feature allows to, for example, ignore all files within a certain directory, except for a
particular one (See examples below).

For example to ignore everything but the `colors` directory under `~/.vim`
```yaml
dotfiles:
d_vim
dst: ~/.vim
src: vim
cmpignore:
- '*'
- '!*/colors/**'
```

To completely ignore comparison of a specific dotfile:
```yaml
dotfiles:
Expand Down
3 changes: 2 additions & 1 deletion dotdrop/cfg_yaml.py
Original file line number Diff line number Diff line change
Expand Up @@ -436,7 +436,8 @@ def add_dotfile(self, key, src, dst, link, chmod=None,
self._dbg(f'new dotfile src: {src}')
self._dbg(f'new dotfile dst: {dst}')
self._dbg(f'new dotfile link: {link}')
self._dbg(f'new dotfile chmod: {chmod}')
if chmod:
self._dbg(f'new dotfile chmod: {chmod:o}')
self._dbg(f'new dotfile trans_r: {trans_r_key}')
self._dbg(f'new dotfile trans_w: {trans_w_key}')

Expand Down
15 changes: 9 additions & 6 deletions dotdrop/comparator.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ def compare(self, local_path, deployed_path, ignore=None, mode=None):
ignore = []
local_path = os.path.expanduser(local_path)
deployed_path = os.path.expanduser(deployed_path)
self.log.dbg(f'comparing {local_path} and {deployed_path}')
self.log.dbg(f'comparing \"{local_path}\" and \"{deployed_path}\"')
self.log.dbg(f'ignore pattern(s): {ignore}')

# test type of file
Expand All @@ -59,7 +59,7 @@ def compare(self, local_path, deployed_path, ignore=None, mode=None):
ret = self._comp_mode(local_path, deployed_path, mode=mode)
return ret

self.log.dbg(f'{local_path} is a directory')
self.log.dbg(f'\"{local_path}\" is a directory')

ret = self._comp_dir(local_path, deployed_path, ignore)
if not ret:
Expand Down Expand Up @@ -100,10 +100,13 @@ def _comp_dir(self, local_path, deployed_path, ignore):
self.log.dbg(f'compare directory {local_path} with {deployed_path}')
if not os.path.exists(deployed_path):
return ''
if (self.ignore_missing_in_dotdrop and not
os.path.exists(local_path)) \
or must_ignore([local_path, deployed_path], ignore,
debug=self.debug):
ign_missing = self.ignore_missing_in_dotdrop and not \
os.path.exists(local_path)
paths = [local_path, deployed_path]
must_ign = must_ignore(paths,
ignore,
debug=self.debug)
if ign_missing or must_ign:
self.log.dbg(f'ignoring diff {local_path} and {deployed_path}')
return ''
if not os.path.isdir(deployed_path):
Expand Down
15 changes: 6 additions & 9 deletions dotdrop/dotdrop.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
from dotdrop.comparator import Comparator
from dotdrop.importer import Importer
from dotdrop.utils import get_tmpdir, removepath, \
uniq_list, patch_ignores, dependencies_met, \
uniq_list, ignores_to_absolute, dependencies_met, \
adapt_workers, check_version, pivot_path
from dotdrop.linktypes import LinkTypes
from dotdrop.exceptions import YamlException, \
Expand Down Expand Up @@ -138,12 +138,12 @@ def _dotfile_compare(opts, dotfile, tmp):
return True

ignores = list(set(opts.compare_ignore + dotfile.cmpignore))
ignores = patch_ignores(ignores, dotfile.dst, debug=opts.debug)
ignores = ignores_to_absolute(ignores, [dotfile.dst, dotfile.src],
debug=opts.debug)

insttmp = None
if dotfile.template and \
Templategen.path_is_template(src,
ignore=ignores,
debug=opts.debug):
# install dotfile to temporary dir for compare
ret, err, insttmp = inst.install_to_temp(templ, tmp, src, dotfile.dst,
Expand Down Expand Up @@ -216,11 +216,11 @@ def _dotfile_install(opts, dotfile, tmpdir=None):
LOG.dbg(dotfile.prt())

ignores = list(set(opts.install_ignore + dotfile.instignore))
ignores = patch_ignores(ignores, dotfile.dst, debug=opts.debug)
ignores = ignores_to_absolute(ignores, [dotfile.dst, dotfile.src],
debug=opts.debug)

is_template = dotfile.template and Templategen.path_is_template(
dotfile.src,
ignore=ignores,
)
if hasattr(dotfile, 'link') and dotfile.link in (
LinkTypes.LINK, LinkTypes.LINK_CHILDREN,
Expand All @@ -244,10 +244,7 @@ def _dotfile_install(opts, dotfile, tmpdir=None):
return False, dotfile.key, None
src = tmp
# make sure to re-evaluate if is template
is_template = dotfile.template and Templategen.path_is_template(
src,
ignore=ignores,
)
is_template = dotfile.template and Templategen.path_is_template(src)
ret, err = inst.install(templ, src, dotfile.dst,
LinkTypes.NOLINK,
actionexec=pre_actions_exec,
Expand Down
90 changes: 34 additions & 56 deletions dotdrop/importer.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
from dotdrop.logger import Logger
from dotdrop.utils import strip_home, get_default_file_perms, \
get_file_perm, get_umask, must_ignore, \
get_unique_tmp_name, removepath
get_unique_tmp_name, removepath, copytree_with_ign, \
copyfile
from dotdrop.linktypes import LinkTypes
from dotdrop.comparator import Comparator
from dotdrop.templategen import Templategen
Expand Down Expand Up @@ -148,7 +149,7 @@ def _import(self, path, import_as=None,

self.log.dbg(f'import dotfile: src:{src} dst:{dst}')

if not self._import_file(src, dst, trans_write=trans_write):
if not self._import_to_dotpath(src, dst, trans_write=trans_write):
return -1

return self._import_in_config(path, src, dst, perm, linktype,
Expand All @@ -165,7 +166,6 @@ def _import_in_config(self, path, src, dst, perm,
1: 1 dotfile imported
0: ignored
"""

# handle file mode
chmod = None
dflperm = get_default_file_perms(dst, self.umask)
Expand Down Expand Up @@ -209,68 +209,46 @@ def _check_existing_dotfile(self, src, dst):
self.log.dbg('will overwrite existing file')
return True

def _import_file(self, src, dst, trans_write=None):
def _import_to_dotpath(self, in_dotpath, in_fs, trans_write=None):
"""
prepare hierarchy for dotfile in dotpath
and copy file
src is file in dotpath
dst is file on filesystem
prepare hierarchy for dotfile in dotpath and copy file
"""
srcf = os.path.join(self.dotpath, src)
srcfd = os.path.dirname(srcf)

# check if must be ignored
if self._ignore(srcf) or self._ignore(srcfd):
return False
srcf = os.path.join(self.dotpath, in_dotpath)

# check we are not overwritting
if not self._check_existing_dotfile(srcf, dst):
if not self._check_existing_dotfile(srcf, in_fs):
return False

# create directory hierarchy
if self.dry:
cmd = f'mkdir -p {srcfd}'
self.log.dry(f'would run: {cmd}')
else:
try:
os.makedirs(srcfd, exist_ok=True)
except OSError:
self.log.err(f'importing \"{dst}\" failed!')
return False

# import the file
if self.dry:
self.log.dry(f'would copy {dst} to {srcf}')
else:
# apply trans_w
dst = self._apply_trans_w(dst, trans_write)
if not dst:
# transformation failed
return False
# copy the file to the dotpath
try:
if os.path.isdir(dst):
if os.path.exists(srcf):
shutil.rmtree(srcf)
ign = shutil.ignore_patterns(*self.ignore)
shutil.copytree(dst, srcf,
copy_function=self._cp,
ignore=ign)
else:
shutil.copy2(dst, srcf)
except shutil.Error as exc:
src = exc.args[0][0][0]
why = exc.args[0][0][2]
self.log.err(f'importing \"{src}\" failed: {why}')

return True
self.log.dry(f'would copy {in_fs} to {srcf}')
return True

def _cp(self, src, dst):
"""the copy function for copytree"""
# test if must be ignored
if self._ignore(src):
return
shutil.copy2(src, dst)
# apply trans_w
in_fs = self._apply_trans_w(in_fs, trans_write)
if not in_fs:
# transformation failed
return False
# copy the file to the dotpath
try:
if not os.path.isdir(in_fs):
# is a file
self.log.dbg(f'{in_fs} is file')
copyfile(in_fs, srcf, debug=self.debug)
else:
# is a dir
if os.path.exists(srcf):
shutil.rmtree(srcf)
self.log.dbg(f'{in_fs} is dir')
copytree_with_ign(in_fs, srcf,
ignore_func=self._ignore,
debug=self.debug)
except shutil.Error as exc:
in_dotpath = exc.args[0][0][0]
why = exc.args[0][0][2]
self.log.err(f'importing \"{in_fs}\" failed: {why}')

return os.path.exists(srcf)

def _already_exists(self, src, dst):
"""
Expand Down