From 827299ef24c96815d2f9dd0b4f746215fc512817 Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Mon, 3 Oct 2016 19:01:03 +0200 Subject: [PATCH] Add CLI for partial_sync --- docs/config.rst | 7 +++++ tests/cli/test_sync.py | 61 ++++++++++++++++++++++++++++++++++++++++ vdirsyncer/cli/config.py | 6 ++++ vdirsyncer/cli/tasks.py | 4 +-- 4 files changed, 76 insertions(+), 2 deletions(-) diff --git a/docs/config.rst b/docs/config.rst index e7ecea5..8b12bd6 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -101,6 +101,13 @@ Pair Section Vdirsyncer never attempts to "automatically merge" the two items. +- ``partial_sync``: Assume A is read-only, B not. If you change items on B, + vdirsyncer can't sync the changes to A. What should happen instead? + + - ``error``: An error is shown. + - ``ignore``: The change is ignored. + - ``revert`` (default): The change is reverted on next sync. + - ``metadata``: Metadata keys that should be synchronized when ``vdirsyncer metasync`` is executed. Example:: diff --git a/tests/cli/test_sync.py b/tests/cli/test_sync.py index 67867f3..327da7d 100644 --- a/tests/cli/test_sync.py +++ b/tests/cli/test_sync.py @@ -449,3 +449,64 @@ def test_conflict_resolution(tmpdir, runner, resolution, expect_foo, assert fooitem.read() == expect_foo assert baritem.read() == expect_bar + + +@pytest.mark.parametrize('partial_sync', ['error', 'ignore', 'revert', None]) +def test_partial_sync(tmpdir, runner, partial_sync): + runner.write_with_general(dedent(''' + [pair foobar] + a = foo + b = bar + collections = null + {partial_sync} + + [storage foo] + type = filesystem + fileext = .txt + path = {base}/foo + + [storage bar] + type = filesystem + read_only = true + fileext = .txt + path = {base}/bar + '''.format( + partial_sync=('partial_sync = {}\n'.format(partial_sync) + if partial_sync else ''), + base=str(tmpdir) + ))) + + foo = tmpdir.mkdir('foo') + bar = tmpdir.mkdir('bar') + + foo.join('other.txt').write('UID:other') + bar.join('other.txt').write('UID:other') + + baritem = bar.join('lol.txt') + baritem.write('UID:lol') + + r = runner.invoke(['discover']) + assert not r.exception + + r = runner.invoke(['sync']) + assert not r.exception + + fooitem = foo.join('lol.txt') + fooitem.remove() + + r = runner.invoke(['sync']) + + if partial_sync == 'error': + assert r.exception + assert 'Attempted change' in r.output + elif partial_sync == 'ignore': + assert baritem.exists() + r = runner.invoke(['sync']) + assert not r.exception + assert baritem.exists() + else: + assert baritem.exists() + r = runner.invoke(['sync']) + assert not r.exception + assert baritem.exists() + assert fooitem.exists() diff --git a/vdirsyncer/cli/config.py b/vdirsyncer/cli/config.py index ae87523..c718920 100644 --- a/vdirsyncer/cli/config.py +++ b/vdirsyncer/cli/config.py @@ -254,6 +254,7 @@ class PairConfig(object): self.options = options self._set_conflict_resolution() + self._set_partial_sync() self._set_collections() def _set_conflict_resolution(self): @@ -278,6 +279,11 @@ class PairConfig(object): else: raise ValueError('Invalid value for `conflict_resolution`.') + def _set_partial_sync(self): + self.partial_sync = self.options.pop('partial_sync', 'revert') + if self.partial_sync not in ('ignore', 'revert', 'error'): + raise ValueError('Invalid value for `partial_sync`.') + def _set_collections(self): try: collections = self.options['collections'] diff --git a/vdirsyncer/cli/tasks.py b/vdirsyncer/cli/tasks.py index ce62149..4d52380 100644 --- a/vdirsyncer/cli/tasks.py +++ b/vdirsyncer/cli/tasks.py @@ -68,8 +68,8 @@ def sync_collection(wq, collection, general, force_delete): a, b, status, conflict_resolution=pair.conflict_resolution, force_delete=force_delete, - error_callback=error_callback - + error_callback=error_callback, + partial_sync=pair.partial_sync ) save_status(general['status_path'], pair.name, collection.name,