From 8489b923b5c3d822d5e149f235f45a61a140808c Mon Sep 17 00:00:00 2001 From: sam Date: Fri, 23 Aug 2019 21:28:33 +0200 Subject: [PATCH] fix: migration of old datasets --- conftest.py | 51 +++- renku/api/_git.py | 11 +- renku/api/repository.py | 68 +++--- renku/cli/_checks/__init__.py | 6 +- renku/cli/_checks/files_in_datasets.py | 54 ---- renku/cli/_checks/location_datasets.py | 46 ---- renku/cli/_checks/migrate_datasets.py | 231 ++++++++++++++++++ renku/cli/_client.py | 5 +- renku/cli/dataset.py | 9 + renku/cli/migrate.py | 38 +-- renku/errors.py | 22 ++ renku/models/_jsonld.py | 24 +- renku/models/datasets.py | 32 ++- renku/models/migrations/__init__.py | 12 +- renku/models/migrations/dataset.py | 45 +++- renku/models/provenance/activities.py | 27 +- tests/cli/test_migrate.py | 162 ++++++++++++ tests/fixtures/old-datasets-v0.3.0.git.tar.gz | Bin 17093 -> 35052 bytes tests/fixtures/old-datasets-v0.5.0.git.tar.gz | Bin 0 -> 17476 bytes tests/fixtures/old-datasets-v0.5.1.git.tar.gz | Bin 0 -> 18542 bytes tests/test_cli.py | 35 --- 21 files changed, 637 insertions(+), 241 deletions(-) delete mode 100644 renku/cli/_checks/files_in_datasets.py delete mode 100644 renku/cli/_checks/location_datasets.py create mode 100644 renku/cli/_checks/migrate_datasets.py create mode 100644 tests/cli/test_migrate.py create mode 100644 tests/fixtures/old-datasets-v0.5.0.git.tar.gz create mode 100644 tests/fixtures/old-datasets-v0.5.1.git.tar.gz diff --git a/conftest.py b/conftest.py index 959eb7332e..74988549ea 100644 --- a/conftest.py +++ b/conftest.py @@ -251,7 +251,24 @@ def data_repository(directory_tree): @pytest.fixture( - params=['test-renku-v0.3.0.git', 'old-datasets-v0.3.0.git'], + params=[ + { + 'name': 'old-datasets-v0.3.0.git', + 'exit_code': 1 + }, + { + 'name': 'old-datasets-v0.5.0.git', + 'exit_code': 1 + }, + { + 'name': 'old-datasets-v0.5.1.git', + 'exit_code': 0 + }, + { + 'name': 'test-renku-v0.3.0.git', + 'exit_code': 1 + }, + ], scope='module', ) def old_bare_repository(request, tmpdir_factory): @@ -261,13 +278,19 @@ def old_bare_repository(request, tmpdir_factory): compressed_repo_path = Path( __file__ - ).parent / 'tests' / 'fixtures' / '{0}.tar.gz'.format(request.param) - working_dir_path = tmpdir_factory.mktemp(request.param) + ).parent / 'tests' / 'fixtures' / '{0}.tar.gz'.format( + request.param['name'] + ) + + working_dir_path = tmpdir_factory.mktemp(request.param['name']) with tarfile.open(str(compressed_repo_path), 'r') as fixture: fixture.extractall(working_dir_path.strpath) - yield working_dir_path / request.param + yield { + 'path': working_dir_path / request.param['name'], + 'exit_code': request.param['exit_code'] + } shutil.rmtree(working_dir_path.strpath) @@ -279,21 +302,29 @@ def old_repository(tmpdir_factory, old_bare_repository): from git import Repo repo_path = tmpdir_factory.mktemp('repo') - yield Repo(old_bare_repository.strpath).clone(repo_path.strpath) + yield { + 'repo': + Repo(old_bare_repository['path'].strpath).clone(repo_path.strpath), + 'exit_code': old_bare_repository['exit_code'] + } shutil.rmtree(repo_path.strpath) @pytest.fixture def old_project(old_repository): """Create a test project.""" - repo = old_repository - repository = repo.working_dir + repo = old_repository['repo'] + repository_path = repo.working_dir commit = repo.head.commit - os.chdir(repository) - yield repository - os.chdir(repository) + os.chdir(repository_path) + yield { + 'repo': repo, + 'path': repository_path, + 'exit_code': old_repository['exit_code'] + } + os.chdir(repository_path) repo.head.reset(commit, index=True, working_tree=True) # remove any extra non-tracked files (.pyc, etc) repo.git.clean('-xdff') diff --git a/renku/api/_git.py b/renku/api/_git.py index 0b96ec5360..11f5ed1149 100644 --- a/renku/api/_git.py +++ b/renku/api/_git.py @@ -34,6 +34,7 @@ from renku import errors from renku._compat import Path +from renku.errors import NothingToCommit COMMIT_DIFF_STRATEGY = 'DIFF' STARTED_AT = int(time.time() * 1e3) @@ -229,7 +230,7 @@ def ensure_unstaged(self, path): pass @contextmanager - def commit(self, author_date=None, commit_only=None): + def commit(self, author_date=None, commit_only=None, allow_empty=True): """Automatic commit.""" from git import Actor from renku.version import __version__, version_url @@ -286,6 +287,9 @@ def commit(self, author_date=None, commit_only=None): if not commit_only: self.repo.git.add('--all') + if not allow_empty and not self.repo.index.diff('HEAD'): + raise NothingToCommit() + argv = [os.path.basename(sys.argv[0])] + sys.argv[1:] # Ignore pre-commit hooks since we have already done everything. @@ -303,7 +307,8 @@ def transaction( up_to_date=False, commit=True, commit_only=None, - ignore_std_streams=False + ignore_std_streams=False, + allow_empty=True, ): """Perform Git checks and operations.""" if clean: @@ -316,7 +321,7 @@ def transaction( pass if commit: - with self.commit(commit_only=commit_only): + with self.commit(commit_only=commit_only, allow_empty=allow_empty): yield self else: yield self diff --git a/renku/api/repository.py b/renku/api/repository.py index 0d1eafc261..28f1dc5b65 100644 --- a/renku/api/repository.py +++ b/renku/api/repository.py @@ -16,7 +16,7 @@ # See the License for the specific language governing permissions and # limitations under the License. """Client for handling a local repository.""" - +import subprocess import uuid from collections import defaultdict from contextlib import contextmanager @@ -99,11 +99,13 @@ def __attrs_post_init__(self): # initialize submodules if self.repo: - check_output([ - 'git', 'submodule', 'update', '--init', '--recursive' - ], - cwd=str(self.path)) - # TODO except + try: + check_output([ + 'git', 'submodule', 'update', '--init', '--recursive' + ], + cwd=str(self.path)) + except subprocess.CalledProcessError: + pass @property def lock(self): @@ -251,42 +253,49 @@ def subclients(self, parent_commit): Submodule.iter_items(self.repo, parent_commit=parent_commit) ] except (RuntimeError, ValueError): - # There are no submodules assiciated with the given commit. + # There are no submodules associated with the given commit. submodules = [] - return self._subclients.setdefault( - parent_commit, { - submodule: self.__class__( - path=(self.path / submodule.path).resolve(), + subclients = {} + for submodule in submodules: + subpath = (self.path / submodule.path).resolve() + is_renku = subpath / Path(self.renku_home) + + if subpath.exists() and is_renku.exists(): + subclients[submodule] = self.__class__( + path=subpath, parent=(self, submodule), ) - for submodule in submodules - } - ) + + return subclients def resolve_in_submodules(self, commit, path): """Resolve filename in submodules.""" original_path = self.path / path - if original_path.is_symlink() or str(path - ).startswith('.renku/vendors'): + in_vendor = str(path).startswith('.renku/vendors') + + if original_path.is_symlink() or in_vendor: original_path = original_path.resolve() + for submodule, subclient in self.subclients(commit).items(): - try: - subpath = original_path.relative_to(subclient.path) - return ( - subclient, - subclient.find_previous_commit( - subpath, revision=submodule.hexsha - ), - subpath, - ) - except ValueError: - pass + if (Path(submodule.path) / Path('.git')).exists(): + + try: + subpath = original_path.relative_to(subclient.path) + return ( + subclient, + subclient.find_previous_commit( + subpath, revision=submodule.hexsha + ), + subpath, + ) + except ValueError: + pass return self, commit, path @contextmanager - def with_metadata(self): + def with_metadata(self, read_only=False): """Yield an editable metadata object.""" from renku.models.projects import Project @@ -299,7 +308,8 @@ def with_metadata(self): yield metadata - metadata.to_yaml() + if not read_only: + metadata.to_yaml() @contextmanager def with_workflow_storage(self): diff --git a/renku/cli/_checks/__init__.py b/renku/cli/_checks/__init__.py index 7d484741c2..b63859df8f 100644 --- a/renku/cli/_checks/__init__.py +++ b/renku/cli/_checks/__init__.py @@ -17,11 +17,11 @@ # limitations under the License. """Define repository checks for :program:`renku doctor`.""" -from .files_in_datasets import check_missing_files -from .location_datasets import check_dataset_metadata +from .migrate_datasets import (check_dataset_metadata, check_missing_files) from .references import check_missing_references -# Checks will be executed in the order as they are listed in __all__ +# Checks will be executed in the order as they are listed in __all__. +# They are mostly used in ``doctor`` command to inspect broken things. __all__ = ( 'check_dataset_metadata', 'check_missing_files', diff --git a/renku/cli/_checks/files_in_datasets.py b/renku/cli/_checks/files_in_datasets.py deleted file mode 100644 index 700dd1907b..0000000000 --- a/renku/cli/_checks/files_in_datasets.py +++ /dev/null @@ -1,54 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright 2019 - Swiss Data Science Center (SDSC) -# A partnership between École Polytechnique Fédérale de Lausanne (EPFL) and -# Eidgenössische Technische Hochschule Zürich (ETHZ). -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -"""Check location of files in datasets.""" - -from collections import defaultdict -from pathlib import Path - -import click - -from .._echo import WARNING - - -def check_missing_files(client): - """Find missing files listed in datasets.""" - missing = defaultdict(list) - - for path, dataset in client.datasets.items(): - for file_ in dataset.files: - filepath = Path(file_.path) - if not filepath.exists(): - missing[str( - path.parent.relative_to(client.renku_datasets_path) - )].append(str(filepath)) - - if not missing: - return True - - click.secho( - WARNING + 'There are missing files in datasets.' - # '\n (use "renku dataset clean " to clean them)' - ) - - for dataset, files in missing.items(): - click.secho( - '\n\t' + click.style(dataset, fg='yellow') + ':\n\t ' + - '\n\t '.join(click.style(path, fg='red') for path in files) - ) - - return False diff --git a/renku/cli/_checks/location_datasets.py b/renku/cli/_checks/location_datasets.py deleted file mode 100644 index d84bd600a2..0000000000 --- a/renku/cli/_checks/location_datasets.py +++ /dev/null @@ -1,46 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright 2019 - Swiss Data Science Center (SDSC) -# A partnership between École Polytechnique Fédérale de Lausanne (EPFL) and -# Eidgenössische Technische Hochschule Zürich (ETHZ). -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -"""Check location of dataset metadata files.""" - -import click - -from .._echo import WARNING - - -def _dataset_metadata_pre_0_3_4(client): - """Return paths of dataset metadata for pre 0.3.4.""" - return (client.path / 'data').rglob('metadata.yml') - - -def check_dataset_metadata(client): - """Check location of dataset metadata.""" - # Find pre 0.3.4 metadata files. - old_metadata = list(_dataset_metadata_pre_0_3_4(client)) - - if not old_metadata: - return True - - click.secho( - WARNING + 'There are metadata files in the old location.' - '\n (use "renku migrate datasets" to move them)\n\n\t' + '\n\t'.join( - click.style(str(path.relative_to(client.path)), fg='yellow') - for path in old_metadata - ) + '\n' - ) - - return False diff --git a/renku/cli/_checks/migrate_datasets.py b/renku/cli/_checks/migrate_datasets.py new file mode 100644 index 0000000000..f3197d3656 --- /dev/null +++ b/renku/cli/_checks/migrate_datasets.py @@ -0,0 +1,231 @@ +# -*- coding: utf-8 -*- +# +# Copyright 2019 - Swiss Data Science Center (SDSC) +# A partnership between École Polytechnique Fédérale de Lausanne (EPFL) and +# Eidgenössische Technische Hochschule Zürich (ETHZ). +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Checks needed to determine dataset migration policy.""" +import shutil +import uuid +from collections import defaultdict +from pathlib import Path + +import click + +from renku.models.datasets import Dataset +from renku.models.refs import LinkReference + +from .._echo import WARNING + + +def dataset_pre_0_3(client): + """Return paths of dataset metadata for pre 0.3.4.""" + return (client.path / 'data').rglob(client.METADATA) + + +def check_dataset_metadata(client): + """Check location of dataset metadata.""" + # Find pre 0.3.4 metadata files. + old_metadata = list(dataset_pre_0_3(client)) + + if not old_metadata: + return True + + click.secho( + WARNING + 'There are metadata files in the old location.' + '\n (use "renku migrate datasets" to move them)\n\n\t' + '\n\t'.join( + click.style(str(path.relative_to(client.path)), fg='yellow') + for path in old_metadata + ) + '\n' + ) + + return False + + +def check_missing_files(client): + """Find missing files listed in datasets.""" + missing = defaultdict(list) + + for path, dataset in client.datasets.items(): + for file_ in dataset.files: + filepath = Path(file_.path) + if not filepath.exists(): + missing[str( + path.parent.relative_to(client.renku_datasets_path) + )].append(str(filepath)) + + if not missing: + return True + + click.secho( + WARNING + 'There are missing files in datasets.' + # '\n (use "renku dataset clean " to clean them)' + ) + + for dataset, files in missing.items(): + click.secho( + '\n\t' + click.style(dataset, fg='yellow') + ':\n\t ' + + '\n\t '.join(click.style(path, fg='red') for path in files) + ) + + return False + + +def check_dataset_resources(client): + """Find missing datasets or files listed in metadata.""" + missing_datasets = defaultdict(list) + missing_files = defaultdict(list) + + for path, dataset in client.datasets.items(): + + if not (Path(dataset.path) / Path(client.METADATA)).exists(): + missing_datasets[path] = dataset + + for file_ in dataset.files: + filepath = Path(file_.path) + if not filepath.exists(): + relative = path.parent.relative_to(client.renku_datasets_path) + missing_files[str(relative)].append(str(filepath)) + + return missing_datasets, missing_files + + +def ensure_clean_lock(client): + """Make sure Renku lock file is not part of repository.""" + lock_file = client.path / '.renku.lock' + if lock_file.exists(): + lock_file.unlink() + + # Add lock file to .gitignore. + gitignore = client.path / '.gitignore' + if str(lock_file.name) not in gitignore.read_text(): + gitignore.open('a').write('\n{0}\n'.format(lock_file.name)) + + +def migrate_datasets_pre_v0_3(client): + """Migrate datasets from Renku 0.3.x.""" + for old_path in dataset_pre_0_3(client): + name = str(old_path.parent.relative_to(client.path / 'data')) + + dataset = Dataset.from_yaml(old_path, client=client) + new_path = (client.renku_datasets_path / dataset.uid / client.METADATA) + new_path.parent.mkdir(parents=True, exist_ok=True) + + with client.with_metadata(read_only=True) as meta: + for module in client.repo.submodules: + if Path(module.url).name == meta.name: + module.remove() + + for file_ in dataset.files: + if not Path(file_.path).exists(): + expected_path = ( + client.path / 'data' / dataset.name / file_.path + ) + if expected_path.exists(): + file_.path = expected_path.relative_to(client.path) + + dataset.__reference__ = new_path + dataset.to_yaml() + + Path(old_path).unlink() + ref = LinkReference.create( + client=client, + name='datasets/{0}'.format(name), + force=True, + ) + ref.set_reference(new_path) + + +def migrate_broken_dataset_paths(client): + """Ensure all paths are using correct directory structure.""" + for dataset in client.datasets.values(): + dataset_path = Path(dataset.path) + + expected_path = (client.renku_datasets_path / Path(dataset.identifier)) + + if not dataset_path.exists(): + dataset_path = ( + client.renku_datasets_path / uuid.UUID(dataset.identifier).hex + ) + + if not expected_path.exists(): + shutil.move(str(dataset_path), str(expected_path)) + dataset.path = expected_path + dataset.__reference__ = expected_path / client.METADATA + + for file_ in dataset.files: + file_path = Path(file_.path) + if not file_path.exists() and file_.path.startswith('..'): + new_path = ( + client.renku_datasets_path / dataset.uid / file_path + ).resolve().relative_to(client.path) + + file_.path = new_path + file_._label = new_path + + _, commit, _ = client.resolve_in_submodules( + client.find_previous_commit(file_.path, revision='HEAD'), + file_.path, + ) + id_format = 'blob/{commit}/{path}' + file_._id = id_format.format( + commit=commit.hexsha, path=new_path + ) + + dataset.to_yaml() + + +def dataset_file_path_migration(client, dataset, file_): + """Migrate a DatasetFile file path.""" + file_path = Path(file_.path) + if not file_path.exists(): + # old-style migrated paths relative to dataset root + if file_.path.startswith('..'): + new_path = (client.renku_datasets_path / dataset.uid / + file_path).resolve().relative_to(client.path) + else: + new_path = (( + client.path / client.datadir / dataset.name / file_path + ).relative_to(client.path)) + file_.path = new_path + + _, commit, _ = client.resolve_in_submodules( + client.find_previous_commit(file_.path, revision='HEAD'), + file_.path, + ) + file_._id = file_.default_id() + file_._label = file_.default_label() + + +def fix_uncommitted_labels(client): + """Ensure files have correct label instantiation.""" + for dataset in client.datasets.values(): + for file_ in dataset.files: + _, commit, _ = client.resolve_in_submodules( + client.find_previous_commit(file_.path, revision='HEAD'), + file_.path, + ) + file_.commit = commit + if 'UNCOMMITTED' in file_._label: + file_._label = file_.default_label() + file_._id = file_.default_id() + dataset.to_yaml() + + +STRUCTURE_MIGRATIONS = [ + ensure_clean_lock, + migrate_datasets_pre_v0_3, + migrate_broken_dataset_paths, + fix_uncommitted_labels, +] diff --git a/renku/cli/_client.py b/renku/cli/_client.py index 2a8c505859..e7ed4267c4 100644 --- a/renku/cli/_client.py +++ b/renku/cli/_client.py @@ -44,6 +44,7 @@ def pass_local_client( commit=None, commit_only=None, ignore_std_streams=True, + allow_empty=True, lock=None, ): """Pass client from the current context to the decorated command.""" @@ -55,6 +56,7 @@ def pass_local_client( commit=commit, commit_only=commit_only, ignore_std_streams=ignore_std_streams, + allow_empty=allow_empty, lock=lock, ) @@ -72,7 +74,8 @@ def new_func(*args, **kwargs): up_to_date=up_to_date, commit=commit, commit_only=commit_only, - ignore_std_streams=ignore_std_streams + ignore_std_streams=ignore_std_streams, + allow_empty=allow_empty, ) stack.enter_context(transaction) diff --git a/renku/cli/dataset.py b/renku/cli/dataset.py index 1544a15012..9397c54028 100644 --- a/renku/cli/dataset.py +++ b/renku/cli/dataset.py @@ -169,7 +169,10 @@ from renku.api._git import COMMIT_DIFF_STRATEGY from renku.api.datasets import DATASET_METADATA_PATHS +from renku.cli._checks.migrate_datasets import check_dataset_resources, \ + dataset_pre_0_3 from renku.cli._providers import ProviderFactory +from renku.errors import MigrationRequired from renku.models._tabulate import tabulate from renku.models.datasets import Dataset @@ -195,6 +198,12 @@ def dataset(ctx, client, revision, datadir, format): """Handle datasets.""" ctx.meta['renku.datasets.datadir'] = datadir + missing_dataset, missing_files = check_dataset_resources(client) + old_datasets = [ds for ds in dataset_pre_0_3(client)] + + if missing_dataset or missing_files or old_datasets: + raise MigrationRequired('datasets') + if ctx.invoked_subcommand is not None: return diff --git a/renku/cli/migrate.py b/renku/cli/migrate.py index f45bd6f566..12284c1584 100644 --- a/renku/cli/migrate.py +++ b/renku/cli/migrate.py @@ -26,12 +26,9 @@ ``renku migrate datasets`` command will take care of it. """ -import os - import click -import yaml -from renku.api._git import COMMIT_DIFF_STRATEGY +from renku.cli._checks.migrate_datasets import STRUCTURE_MIGRATIONS from ._client import pass_local_client @@ -43,35 +40,16 @@ def migrate(): @migrate.command() @pass_local_client( - clean=False, + clean=True, commit=True, - commit_only=COMMIT_DIFF_STRATEGY, + allow_empty=False, ) @click.pass_context def datasets(ctx, client): """Migrate dataset metadata.""" - from renku.models._jsonld import asjsonld - from renku.models.datasets import Dataset - from renku.models.refs import LinkReference - - from ._checks.location_datasets import _dataset_metadata_pre_0_3_4 - - for old_path in _dataset_metadata_pre_0_3_4(client): - dataset = Dataset.from_yaml(old_path, client=client) - - name = str(old_path.parent.relative_to(client.path / 'data')) - new_path = (client.renku_datasets_path / dataset.uid / client.METADATA) - new_path.parent.mkdir(parents=True, exist_ok=True) - - dataset = dataset.rename_files( - lambda key: os.path. - relpath(str(old_path.parent / key), start=str(new_path.parent)) - ) - - with new_path.open('w') as fp: - yaml.dump(asjsonld(dataset), fp, default_flow_style=False) - - old_path.unlink() + results = [ + migration(client) is not False for migration in STRUCTURE_MIGRATIONS + ] - LinkReference.create(client=client, - name='datasets/' + name).set_reference(new_path) + if all(results) and client.repo.index.diff(None): + click.secho('OK', fg='green') diff --git a/renku/errors.py b/renku/errors.py index fcb46d82c6..f68fcaae3c 100644 --- a/renku/errors.py +++ b/renku/errors.py @@ -155,6 +155,28 @@ def __init__(self, ignored): ) +class MigrationRequired(RenkuException, click.ClickException): + """Raise when migration is required.""" + + def __init__(self, resource_type): + """Build a custom message.""" + super(MigrationRequired, self).__init__( + 'Broken resource of type `{0}` found.\n' + 'Migration is required.\n' + 'Please check `renku migrate --help` ' + 'command to fix the issue.\n' + 'Hint: `renku migrate datasets`'.format(resource_type) + ) + + +class NothingToCommit(RenkuException, click.ClickException): + """Raise when there is nothing to commit.""" + + def __init__(self): + """Build a custom message.""" + super(NothingToCommit, self).__init__('There is nothing to commit.') + + class FailedMerge(RenkuException, click.ClickException): """Raise when automatic merge failed.""" diff --git a/renku/models/_jsonld.py b/renku/models/_jsonld.py index f7d0caae6f..4c874e24bf 100644 --- a/renku/models/_jsonld.py +++ b/renku/models/_jsonld.py @@ -24,6 +24,7 @@ from datetime import datetime, timezone import attr +import yaml from attr._compat import iteritems from attr._funcs import has from attr._make import Factory, fields @@ -31,7 +32,7 @@ from renku._compat import Path from renku.models._locals import ReferenceMixin, with_reference -from renku.models.migrations import MIGRATIONS +from renku.models.migrations import JSONLD_MIGRATIONS KEY = '__json_ld' KEY_CLS = '__json_ld_cls' @@ -346,6 +347,22 @@ def from_jsonld( data.setdefault('@context', cls._jsonld_context) + schema_type = data.get('@type') + migrations = [] + + if isinstance(schema_type, list): + for schema in schema_type: + mig_ = JSONLD_MIGRATIONS.get(schema) + if mig_: + migrations += mig_ + + if isinstance(schema_type, str) and not migrations: + migrations += JSONLD_MIGRATIONS.get(schema_type, []) + + for migration in set(migrations): + data = migration(data) + __source__ = migration(__source__) + if data['@context'] != cls._jsonld_context: try: compacted = ld.compact(data, {'@context': cls._jsonld_context}) @@ -354,9 +371,6 @@ def from_jsonld( else: compacted = data - for migration in MIGRATIONS: - data = migration(data) - fields = cls._jsonld_fields data_ = {} @@ -405,8 +419,6 @@ def asjsonld(self): def to_yaml(self): """Store an instance to the referenced YAML file.""" - import yaml - dumper = yaml.dumper.Dumper dumper.ignore_aliases = lambda _, data: True diff --git a/renku/models/datasets.py b/renku/models/datasets.py index 54f282e22e..daa68c1af8 100644 --- a/renku/models/datasets.py +++ b/renku/models/datasets.py @@ -217,7 +217,8 @@ def _now(self): @filename.default def default_filename(self): """Generate default filename based on path.""" - return Path(self.path).name + if self.path: + return Path(self.path).name @property def full_path(self): @@ -234,7 +235,10 @@ def size_in_mb(self): def __attrs_post_init__(self): """Set the property "name" after initialization.""" - self.name = self.filename + super().__attrs_post_init__() + + if not self.name: + self.name = self.filename def _parse_date(value): @@ -248,8 +252,11 @@ def _convert_dataset_files(value): """Convert dataset files.""" coll = value - if isinstance(value, dict): # compatibility with previous versions - coll = value.values() + if isinstance(coll, dict): # compatibility with previous versions + if any([key.startswith('@') for key in coll.keys()]): + return [DatasetFile.from_jsonld(coll)] + else: + coll = value.values() return [DatasetFile.from_jsonld(v) for v in coll] @@ -474,7 +481,10 @@ def unlink_file(self, file_path): def __attrs_post_init__(self): """Post-Init hook.""" - self._id = self.identifier + super().__attrs_post_init__() + + if not self._id: + self._id = self.identifier if not self._label: self._label = self.identifier @@ -483,16 +493,18 @@ def __attrs_post_init__(self): self.path = str(self.client.renku_datasets_path / str(self.uid)) if self.files: - for datasetfile in self.files: - if datasetfile.client is None: + for dataset_file in self.files: + file_exists = Path(dataset_file.path).exists() + + if dataset_file.client is None and file_exists: client, _, _ = self.client.resolve_in_submodules( self.client.find_previous_commit( - datasetfile.path, revision='HEAD' + dataset_file.path, revision='HEAD' ), - datasetfile.path, + dataset_file.path, ) - datasetfile.client = client + dataset_file.client = client try: self.commit = self.client.find_previous_commit( diff --git a/renku/models/migrations/__init__.py b/renku/models/migrations/__init__.py index 8d3c7bcaae..6dc9b470a6 100644 --- a/renku/models/migrations/__init__.py +++ b/renku/models/migrations/__init__.py @@ -15,8 +15,14 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -"""Renku migrations.""" +"""Renku JSON-LD migrations.""" -from renku.models.migrations.dataset import migrate_dataset +from renku.models.migrations.dataset import ( + migrate_dataset_schema, + migrate_absolute_paths, +) -MIGRATIONS = [migrate_dataset] +JSONLD_MIGRATIONS = { + 'dctypes:Dataset': [migrate_dataset_schema, migrate_absolute_paths], + 'schema:Dataset': [migrate_absolute_paths], +} diff --git a/renku/models/migrations/dataset.py b/renku/models/migrations/dataset.py index 272c58c928..3ffb988fe1 100644 --- a/renku/models/migrations/dataset.py +++ b/renku/models/migrations/dataset.py @@ -16,16 +16,53 @@ # See the License for the specific language governing permissions and # limitations under the License. """Migrations for dataset.""" +import os +from pathlib import Path -def migrate_dataset(data): +def migrate_dataset_schema(data): """Migrate from old dataset formats.""" - if data.get('@type') != 'dctypes:Dataset': - return data + if 'authors' not in data: + return - data['creator'] = data.pop('authors', {}) + data['@context']['creator'] = data['@context'].pop( + 'authors', {'@container': 'list'} + ) + data['creator'] = data.pop('authors', {}) for file_name, file_ in data.get('files', {}).items(): file_['creator'] = file_.pop('authors', {}) return data + + +def migrate_absolute_paths(data): + """Migrate dataset paths to use relative path.""" + raw_path = data.get('path', '.') + path = Path(raw_path) + + if path.is_absolute(): + try: + data['path'] = path.relative_to(os.getcwd()) + except ValueError: + elements = raw_path.split('/') + index = elements.index('.renku') + data['path'] = Path('/'.join(elements[index:])) + + files = data.get('files', []) + + if isinstance(files, dict): + files = files.values() + + for file_ in files: + path = Path(file_.get('path'), '.') + if path.is_absolute(): + file_['path'] = path.relative_to((os.getcwd())) + + return data + + +DATASET_MIGRATIONS = [ + migrate_absolute_paths, + migrate_dataset_schema, +] diff --git a/renku/models/provenance/activities.py b/renku/models/provenance/activities.py index 5e5174faeb..4c12f1a8bc 100644 --- a/renku/models/provenance/activities.py +++ b/renku/models/provenance/activities.py @@ -18,6 +18,7 @@ """Represent a Git commit.""" import os +import uuid from collections import OrderedDict from pathlib import Path @@ -28,6 +29,7 @@ from renku.models.cwl import WORKFLOW_STEP_RUN_TYPES from renku.models.cwl._ascwl import CWLClass from renku.models.cwl.types import PATH_OBJECTS +from renku.models.refs import LinkReference from .agents import Person, renku_agent from .entities import Collection, CommitMixin, Entity, Process, Workflow @@ -108,13 +110,11 @@ class Activity(CommitMixin): def default_generated(self): """Entities generated by this Action.""" results = [] - for path, role in self.outputs.items(): client, commit, path = self.client.resolve_in_submodules( self.commit, path, ) - output_path = client.path / path parents = list(output_path.relative_to(client.path).parents) @@ -178,11 +178,24 @@ def parents(self): @property def paths(self): """Return all paths in the commit.""" - return { - item.a_path - for item in self.commit.diff(self.commit.parents or NULL_TREE) - # if not item.deleted_file - } + index = set() + + for file_ in self.commit.diff(self.commit.parents or NULL_TREE): + path_ = Path(file_.a_path) + is_dataset = self.client.DATASETS in str(path_) + not_refs = LinkReference.REFS not in str(path_) + does_not_exists = not path_.exists() + + if all([is_dataset, not_refs, does_not_exists]): + uid = uuid.UUID(path_.parent.name) + path_ = ( + Path(self.client.renku_home) / self.client.DATASETS / + str(uid) / self.client.METADATA + ) + + index.add(str(path_)) + + return index @classmethod def generate_id(cls, commit): diff --git a/tests/cli/test_migrate.py b/tests/cli/test_migrate.py new file mode 100644 index 0000000000..3dec5312e0 --- /dev/null +++ b/tests/cli/test_migrate.py @@ -0,0 +1,162 @@ +# -*- coding: utf-8 -*- +# +# Copyright 2017-2019 - Swiss Data Science Center (SDSC) +# A partnership between École Polytechnique Fédérale de Lausanne (EPFL) and +# Eidgenössische Technische Hochschule Zürich (ETHZ). +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Test ``migrate`` command.""" +from pathlib import Path + +import pytest + +from renku import LocalClient, cli +from renku.cli import RENKU_HOME + + +@pytest.mark.migration +def test_status_with_old_repository(isolated_runner, old_project): + """Test status on all old repositories created by old version of renku.""" + runner = isolated_runner + result = runner.invoke(cli.cli, ['status']) + assert 0 == result.exit_code + + output = result.output.split('\n') + assert output.pop(0) == 'On branch master' + assert output.pop(0) == 'All files were generated from the latest inputs.' + + +@pytest.mark.migration +def test_update_with_old_repository(isolated_runner, old_project): + """Test update on all old repositories created by old version of renku.""" + runner = isolated_runner + + result = runner.invoke(cli.cli, ['update']) + assert 0 == result.exit_code + + output = result.output.split('\n') + assert output.pop(0) == 'All files were generated from the latest inputs.' + + +@pytest.mark.migration +def test_list_with_old_repository(isolated_runner, old_project): + """Test dataset list on old repository.""" + result = isolated_runner.invoke(cli.cli, ['dataset']) + + assert old_project['exit_code'] == result.exit_code + assert not old_project['repo'].is_dirty() + + +@pytest.mark.migration +def test_migrate_with_old_repository(isolated_runner, old_project): + """Test migrate on old repository.""" + result = isolated_runner.invoke(cli.cli, ['migrate', 'datasets']) + assert 0 == result.exit_code + assert not old_project['repo'].is_dirty() + + +@pytest.mark.migration +def test_correct_path_migrated(isolated_runner, old_project): + """Check if path on dataset files has been correctly migrated.""" + result = isolated_runner.invoke(cli.cli, ['migrate', 'datasets']) + assert 0 == result.exit_code + + client = LocalClient(path=old_project['path']) + assert client.datasets + + for ds in client.datasets.values(): + for file_ in ds.files: + path_ = Path(file_.path) + assert path_.exists() + assert not path_.is_absolute() + assert file_._label + assert file_._id + assert file_.path in file_._label + assert file_.path in file_._id + + +@pytest.mark.migration +def test_author_to_creator_migration(isolated_runner, old_project): + """Check renaming of author to creator migration.""" + client = LocalClient(path=old_project['path']) + if client.datasets: + dataset = client.datasets.popitem()[1] + dataset_path_pre40 = Path(dataset.path.replace('-', '')) + if dataset_path_pre40.exists(): + metadata = (dataset_path_pre40 / client.METADATA).read_text() + + assert 'authors:' in metadata + result = isolated_runner.invoke(cli.cli, ['migrate', 'datasets']) + assert 0 == result.exit_code + + after_metadata = (Path(dataset.path) / client.METADATA).read_text() + assert 'creator:' in after_metadata + assert 'authors:' not in after_metadata + + +@pytest.mark.migration +def test_correct_relative_path(isolated_runner, old_project): + """Check if path on dataset has been correctly migrated.""" + result = isolated_runner.invoke(cli.cli, ['migrate', 'datasets']) + assert 0 == result.exit_code + + client = LocalClient(path=old_project['path']) + assert client.datasets + + for ds in client.datasets.values(): + assert not Path(ds.path).is_absolute() + assert ds.path.startswith(RENKU_HOME) + + +@pytest.mark.migration +def test_remove_committed_lock_file(isolated_runner, old_project): + """Check that renku lock file has been successfully removed from git.""" + repo = old_project['repo'] + repo_path = Path(old_project['path']) + with open(str(repo_path / '.renku.lock'), 'w') as f: + f.write('lock') + + repo.index.add(['.renku.lock']) + repo.index.commit('locked') + + result = isolated_runner.invoke(cli.cli, ['migrate', 'datasets']) + assert 0 == result.exit_code + + assert (repo_path / '.renku.lock').exists() is False + assert repo.is_dirty() is False + + ignored = (repo_path / '.gitignore').read_text() + assert '.renku.lock' in ignored + + +@pytest.mark.migration +def test_graph_building_after_migration(isolated_runner, old_project): + """Check that structural migration did not break graph building.""" + result = isolated_runner.invoke(cli.cli, ['migrate', 'datasets']) + assert 0 == result.exit_code + + result = isolated_runner.invoke(cli.cli, ['log']) + assert 0 == result.exit_code + + +@pytest.mark.migration +def test_migrations_run_once(isolated_runner, old_project): + """Check that migration commit is not empty.""" + result = isolated_runner.invoke(cli.cli, ['dataset']) + assert old_project['exit_code'] == result.exit_code + + result = isolated_runner.invoke(cli.cli, ['migrate', 'datasets']) + assert 0 == result.exit_code + + result = isolated_runner.invoke(cli.cli, ['migrate', 'datasets']) + assert 1 == result.exit_code diff --git a/tests/fixtures/old-datasets-v0.3.0.git.tar.gz b/tests/fixtures/old-datasets-v0.3.0.git.tar.gz index 0bec2ee8e3529eaff48c511c6be4c2503ac9a098..35ac13b5afacf44687178e1ff8ebcd87946b7d0c 100644 GIT binary patch literal 35052 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 ~=xuN;f$9t<&fWHnKNFP*(6H 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 literal 17093 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 AaR2}S diff --git a/tests/fixtures/old-datasets-v0.5.0.git.tar.gz b/tests/fixtures/old-datasets-v0.5.0.git.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..1d14a869141be25a3ac1ac6e4cd4bf14bbdc1a2a GIT binary patch literal 17476 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 z5$Wvd+PiCMHcFO%D!=>(XcoTza0O>H2Ff=8000000000000000000000000000000 T00000z@5RrnL2w30FVIyq)eX% literal 0 HcmV?d00001 diff --git a/tests/fixtures/old-datasets-v0.5.1.git.tar.gz b/tests/fixtures/old-datasets-v0.5.1.git.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..da7f8e88b64f893b1c7219411965168fd1153a14 GIT binary patch literal 18542 zcmV)UK(N0biwFP!000001MEE8cH2gheigo=193x9CP0E$S+cxQBsu0;Tkjt@WU4)#3XC*1Ss~t}00s z^N_Iz5g%P!Z`t?1XbG;M|1juBcf|y^r+)+fx1#@jlxyk#>~Qbk4#q$Nw;GMB=x;UJ zwMF_j+d%*79gMxSeC71tY6`c-A8pxNTf*AntzN4pdRE(NbJ6ZKYrN(g*K=3MiSglp7Yj!XidGR*uPQ2J2cz&P7 z0UPls7NK_I;q6A-ZMGhXt#+&Dw5pG`+Ua^W=WN4d+f0t z_%XNR@BRTC3olGB$UuYc7f^_>xZY4`Hy)$jcCDTeZHJ3qm2->No372bn)SNlZ1w8A z*QnN;y?S#wFjng_JZ&=x8RmQ~+!YCX{u^*Rh!twa4s zwH|LcEz5D-DzCMix_fH^UjaBmIiv0jvwyba-c7ky|A)f83n$?A_P=SttCjt~hq9dh z15jFD#s#;hf6Hn$R`kD*a#Q+CGyHjDEc5@GwfX}6!93Pi{@;C+>J7{N`G0J~^TAEv z!_v0u3xBgK7yD*X?1biMxFjIl)jSL^i)@qfLw(*O5TezJp5yw!lS z$3f)9K{$1TaKz(t5k_9%vt3rz){w17fg|8o9410r>+ukOxO^DFFYp!ka0t#qG6q%> zQCjUMXa`OM(}!-P!x;8ZaO@HI%Yau*{x%HuAFj3*jS{CXa$nU8^?4iywuqwKciFk2 zKHv=ACte6@Lyi7tWA?96u9E*j5WKsq1n_6%f9thX{C6+qdisw+Er6&S(n1@fsBZ!u z#zS$dF_zH3)~+??>0h&Ijg|htm$Lp%rRVvTXrQfY>uk?w;sX(wAp@|;4!v=VX;{Sx zEFK8uuQ5>jYy_&8_k{^<_rV5$_U%oX4h*C!>1^V~18U{-kzj#K&uCEKfyZpXBYXqx zRj;bYGIN9BFqp_5v5g}nG-#DnH?j$|RgH>ZHG(7r?~SpOft&-p8z&(cH^ICJXCfRJ z<+9cU6}0Tg&Iti7_lA+21808V8^4M$V8AkQ5|yE84js@t?*(Bj9FuT)0z>({M-zo_ z*j*W2PecQ~kw^%beo@MFQ^Ow8b`SyC!vqij4MQPP8bv0}()Gd!;v-)`@1x*cqzKj5 z*-NmnFmd6d8P36)lFc-AHW0&cj*c|0V37b1(8&)B#7p9F5+m}7!j9*w0}(I7Z;Q2JqO>^NAdDfD@0}+=p(W z*c%Q3B4QvQGL!Uy|A#<43|d$}2mob{;6T3p;}GOWQh;djy6V>pUq`7t(YcMTQ5{f zMWz=PpwN-6Y;2x=lF*b32#XG+g9@ztF&2O*@a4)S{LR?q00Je7@;ej>ss6^-naO_1 zF^J7=pvnbK`2||8G0$ZHZH0aNEh`WsKYYk`!RfpVc(${HqivStgNV3|^XVRczgyVA z0nb5`{{Ssy!;`~TXPtw?qZ9TK*ckxuSuv{o{AR=4eDbDbZWbzY46x1KSe0=RCyaf0 zh+mM**oZ?=0^knA3_RZewAkpCsUEqPxWh3seqF@3E_DZC`x@lta0-2W zBQbP_oci$LrQ}@)ZE!2kcd7k1#z{2zG8?nZ|7%#S#r$v8s;%t*eUzE~M>WDhof_v^ zi1no6i1Uh{fZnS;`PO0|KeFGLum=Hq@@);-k~H8fbVOw+69)+fVxaEX430yO@AR)QW?UlwK@=OHz3q2kq(6I%rSe~2 zjQ`rz`pW;mm-25f|9K9+QVHZMeO;Phx&7zk@o);Hu?L?M`(s%q|E=bN{MV|DR&^!+ z_fc*d|DTJ{bEj;A@sNyk6MElEbCLX1mOnKTY^5CB-1qlL7H$)Ek+A^Pt7+1Ew zh!uOFQFbwrh~Y;l{vqsm))ab~41_P!#`2?{?-RWdfa`OT?kQ`dn#odiKG=!Jvsr)jS0IoZ?~$gO$O8tw$KZn&WvGvb-Ohu(AL((IUh0|{v=}e z&4sGa3Di-kS*k?ISZJ0aXtM;r=qJN8Kvjcx6 z0%cF=a9KD4=26#RUWJVfk3D2ose~Z=Lc3)yaaI0$<$C@9#jYQfCHlWzy|DkMy~_XI zQ@N@B?+pX{9ZP)f`vHVy-~h(FA4wfVOHmM;U9t+CswH$9)mktQ`w1@ZMYz4wCCwZS zxMct*$_HOeZmG*NZxNgAQvwPe3@Y$Iyliq@PR>n$mR=}<)e8dJiG(tM#s+k1cyX!| zwfx7`vUIx2N&U>cVUw}%F%{+VWJv4G5Htt4aM`0(@*FIbt`gAq5=gSN))ioU5fG!m zfw+D-5FAGbFJOveQzH)sm!UarbOlpv<_4wiFdn~d?mY++?7>Z^FKdgtM1dpxsobV} zBTz8u7~@i}obhEaL64r|EeX~&Aj#EZH49{3$+W=1G82pIe43JxUAm~AQ3lTAsSD8ZBoQv=dq*#CTER;c2(TlWcjEaw9caxza!X-(;yfd7%3 zhQb6mTY-EbThD&WPA*I{!SvAfHKSe}G^GP+l10U#7Mo?F8!95JT%lu1+68W3);eFe zq%AV&HLVw`&W#Pid$WO|qH36FhGh-2iY3}zfXqiJ0YAv^+0n*ffW9Zbf*xr>Ua+F| zzBuF1O0K?M(v&xxYe+t^e(ED1AXrf5Ixw*++%4T!>hR9+JcqH|JAn~53 zKDx>2#scC2noc|&i%2f$D=tIHMd!sdP5h?}x~_vAbBk12Ks4+56tIAcQ)J^n+i>6}BVuoh#K&ZIQ*9@6 zVZoU9(J`L&l?GFQRT*mK@g~V5@ut{9Vl0n}O#YT4;vlt%(kS0z#@9kg5{w`&(lrld zChT)iV*YwQ2cGg2d&1@y5uv|JNDI>_rcH`470_eO6xRfzI5IDx*b4&V7C2Z+DtXLp zp2w9_kYOM-I5TMkR?!QKnwKhEL%;phd*Et>FDGFN{Uw~XIEf2Z<~ICxSMW+tY7yUY z2w}p^t-8FyWx{l!-CfLeq1P{(^rnP&&!5J0u>opo@Sp=;nD}InFcl{wA5=2W*seCh|f!!LTed@r~9Q*?li#XzWNI^9f9 zJA~90@k||J6ZF$nXEY3L&!2^>sI9Krp^y5S`bnd6)rs1GknOGqxFqb^VFh{k0Gon4 z0~!^k5N%Vlf`v!Yj&g??)I0zilYwW;_Ow8OX#|0NP%<5w*0Aa6tLMw>LhX)RXp!K@ z14MWdNz0UmKXTjwo^8moYS~=H`-BhWR6D*r)Q2IEB3-sIB2tjx;;w{1A_*a)aGvr! z5oG~zV}&~O00{OS9-L5S2ft9q3UVh>O7OLhSeSu= zzJeSEdN|D-4RXq7Zqckm@HH;$a0Z-d&i9THDE7Udr3`>d>9L$Ob!2__mo%^#dUAaZ z1rVq=X2qYOKBf<7DE*fdA+&~3i=J!Jhv9FX#7vg`89#d(M-H`=DMn4qrZfUcx;B z(A}Zu^o9TX|3r}&flt9ss5L!43+w?rB+&ftzyEKDwr%6^~71nK=$R6g{y3;hltZ0Y{ldfg4Z27r=d7pl?GD z+_E`jrO?5g^A*su0JXc;{h%pyLF(KsWJxSPZykBxPOm;-=PbLfeTh3&bX4fFE* z37Y;sZbb9Rl@Z|xSd+G%NX8j@pv<`3jB;U8bm|h~V26xjw#r5Kkd$e63Tb@7!{L+; zcgf&A6qrv2r`6P693OO^b`B5dI96AsU-Jhg10R~^&cxw2LtbUD`=TV%zTbfPn(t_q ztYAzT9l1>Lqx8#ZTaCIbCjxqcPD6io2Z01OEi)jgpH0BbEvjtfw*6BFrMa=tU>0S^ z$nHR8+}65-Mt^Sb;RxCu9=NaXA41R%=KMN5m6&VWY_{;dwF_-h?hX41E~&}Mljpo=F$4ocW*!G zr8L*R*vUmipxDwO=y0lTr&ReD&_yDoj4APoSyWB3mM`Xbi7 zoBDrWk^x$h|EXJz#q*!-R<*VA|L&vQ<@t|gw?O#8EKQNSp+TJh!4us#J_B;a2@qVO zz1uS&Y65bH=;ARCg%rFdd-1HC+APZZ9PQRFR#D5dn2w?Q?O1jv8~O*TCLNa+W|tG)tUwglph(;^!Rj z*^`sUB@$C^1b{YpE1I`#A3$1Mn=;{+rk6qmPGyx&g%Hb}W4- zxqpa`+Y<&Tqo8s~OnwJP6Horz10F&%+!BTEl<%nIW9$tbVQTq>rxYQJOQpyIM>2J% zx>g&jd${6+8;*EL`3YQuQlJ2NKvK}(EVB*VYk-~L{4+1VOdbUux!^r-=phFMtQ!o0 zs1dWO#b|cVKpd99)At}c!Cq)??-NP@N&rB7@fxoI2pR6Y4)CI{VVdJ}MCA2-a&`zb zQ(L2b&~v`-{GAWV>COAPd+2@^Ql|f?tugj||7`F1^WA-%wBfKKc&Pt;`=(lCAKAoa zhFwB5R+V7cfVszy-y9x4)egfj2)Ef@jF(nOUUwXo44yF!B~3w$DZPm9tGrnR7!K$? zikLSqE|e)G!j46N`VeoOq39&#)d)M28F0e+kgh2~-RPWqQ7T87Vo}%2Xqu8sT*s5@ zbgH~cCqUk%%~SxMllDRIQihDuzuOu(YB@Q)L;&UeXNa2lJx!fl#DZC0ARDo?Kmbj+ zzaQM-{C{n6|97iiTls(YQof4w|HQ%99{#5qU)RZhS+19VHyQ;#lGd2uF=*hAKj8kq zR=wR`y#H@?|LZ-KT>c+#pVBmxGED2*@eVy><`85mlD|4H_qVr?!7HroT)$xeug~gZ zN{qc$oCDQ2FpnK=S1RCm3kV2~$yF4|~}N$O;rrVV{OsDLYt zsH)~Ov$zFoSi`Yosjt4poTubjf zyM- zcu}yKDTKU^oo`=+(MMCZQBX)PvjR@G#CF+td#`@@kr+p9tIlYJi$%y3DWxdf&bq)G z;ecMhVYC9!z}q-aE+U1Z+!_rAW1%Z6pDPgkq-96@B=_DyUycVF|J27#4s>lJlk=EO zkw8m9D5k&^O}gR^BNoQ=r$BsK4Cgo>fCGGYlo!(^d!v2Q5E%tP))^A;45_yxC-?I) z+x;gij-n}8L@=%C3wzq1cz%)QJKqqd_1g36wf|%9Nx+&qwpMH3C#h)V@oUxkyhfCO zf$RwcK@q_P1*#%eK)gwAAP5O$5fEvu>#IxMTCLVn+lR472ulq5-# zNcgj5uZa|DRz)NDSgX~OE?JnZj2#{6Xywt4WzY_@J}rgYW2hytIdqiU=V%~-2ei#H zK^|rcFbYwUe?;E_Sg$Rn*x&obh$&xZqkwdxn1*b7_B>xdvb1Oe@D7nq{P5L-F=#2D zPEWG_MBjoj4R0@JT!JmtF`yF3VSM3AWW&UT;}-C32VKWqG>s824Q{&}B@@&FsU`<* zR3OaYlm{m8P!S=xi9;|OA_eVTEnK1v_NDMcbLk8<5XI7ZVmSnf2hpgQg^oD>U{fR> zUdUM-zy@Yvt&sRK4*_>(J(9%mr3@2B2B=}svKUO*R$;an*vp&{ai$D|O$gPnU5x9* zrL*B(|KJjj6ONiI1Lay&)EQ&V1V16vkI`YpS7t<-HprLA1w`2#8ITu(oi*s^5MZew zK!OR>xr{uXn!`A6=B89L_=sUQC(LSTygO_R8l}g9iXuJ!-w%!oY~w>{hBqJuvP5P$ z2nWS|&>1!h>>uvMc|72q31qREU`{C2y}QsOGN!i>BB>Jz+Z2kC)L4O-&?oueGQ#eZ zmW(ULAF&Irr4ofW;3AoP%v(PEmBo%aNfg4R-iUuAU9p@LjYwvPnmcO?iLbmcIT;*Y z3eZ0YPyysk2S&&e9Xpc{NW~6LW9-KvWR?hoEDKmxLn(uW$|o*cK9UfsJ4e4^1wc@k zD!Ef#>kJb^#Ott{8Bo@7DP3z%s5r952>_E#31N|I8p&ivduig1WUZ@*Cm5KdR^`wB zHA*rfDPI!8ZhpGWVou2#m5SiOA^iC`jGi#l{)1y40;39IpdJO7 zpINGC9T)eQ)^Tyrx`2bj0!|YO9*(@2OSrH`ApCZa!piohuzaQrck2yGFkl>?=MYX|0 zdPfc#LRaikg1B4wUXEQW}I-tX1w8`M0Ggz%9x4S|~k`!+fAqDsIA05*L zAzX}s6tK54zu;lrlQIZbOV|i>XTgtBAdJJZg-{FR0MN0lOANF`;3d%a%*S2e?I|b# zL8rd2U-e#UlmGK+9RO?A|GE4Bt5x!b_h0Lz{7>lrSo3eS**}Sc%>SGQur~R>bm-f^ z&i_LuQz|Rve^H_84fJ0JrK10b%ez;k_oL)esag#G22uSTW|M`Ap!(8?M_8PSHlYha zgifs+B$VJk_`oKDA(dE@I<|FUZw(ujZqX6vN-%qJK_2`B6xgzldc`iCI(#>?b73){ z4Xo%ZQdtf7q`^JdBAaang!~ucK)He*UaSFeL2YR zDk2Hd&I3^Fd|`v;mIOy`<=|`{*W zR^Z4QF29=4r!&N;$XPQ!o=&Kc7sCwEH^fnfe22q7YW8lS~G@HhjlSqd~rk-067b1g=2`{R?(l28G>SYol-NAjA>AJg1bs%Y+_ zIvyM(e*v#&Zl&4f=h2;WG#8L$*pXPlP-*8l3}OSWs@gh^gP^M>BuN2{Bqk4T(>?yw zhH;TD;ulG&EADfcu0;bXjvr)*iX9fKnLSVCY?nTi;@wdL}@@U9bioE=0{--h=&NB zT%lo<3=9Gu1(Yo-!(?6nX ztFmu-vLDd^VWm?iqKTw@c<|@r0RzI&DiyF*iGm8P758^V{Skaa0Puh?4I}S^0zswP zLW&zemdq|8u7-YmUHnhT2T53&-|)Sqx_3|N9UGB?m8kqfnV30gBa`Z&tp;0_=dsHG z=sG5O-icNw2d+KX7mb+pjDX#=&E|lxUKY8*&&!{R1zUsL$pIn=n|V*jf-ZLchRFi> z6@(42ScWszff5NEvM3Q7WkXbYh>b#+jhXukB4Qsiu9NTT(Hekd11zw-5uCsXNa{Dx zWZ{riHfkH+>e!GYox_x2;GKCy2c@@I0SagjF*YXPx(Ju(Q8@etn|<9O;>;{MoQ4|P zaA@2CJ{?V2Tu>DU9PimKVAsUbjpk%#;1yBYnul2oI|I-p;xm{*44uKImQf6oMQ*FC z;6pOrGGy5j5Z}Xx1%IxXOM+`AK?c&ASd*~n5>E+6s@cwrBykL-w(gYXs- zcR=*-WI+)}fuJ>zHv}Sib*qXO)LnXV(gyArTlkz_s3gIT37+R02up;J9lTw^MkaQX zeQ@h42pe6H_;bX8kK8%P&VcL&CJAoQu``6ri<``Z2A>W)USiI>6sQR|ckx0bm6>?# z#%YBlBqwH9mX#IR!GU@31`?seK8?<@xh{=1@S6bQ@k4uuZboU&M2Q3s39E4}rCC5hW-Rn*~q z3O{<(oAZb2Rk@~w_v0!_l`8V<=`e^96>75eBvkcXO7N=JW&vFb$({28LAV%A0Vq6z z#Pd~PB7~KPV|syCHI1fR*65F8OKcqF5DAv$Nw`ff+{VZzQX(%}R!wzmTkg=t)_B5= zVP5bjR;&Vv5MIrlqRImSix_6(sQiJ_Rrmpn< zw?@_Q{!87I+Q$Fsoeq0X{`xa8Q7-ULjQenZ3mUsPx?Y2Jf2O!ntY8(9g7}5rCy5oT zGE&g_b})ZTSCZx`-jA;Mdh`=|Ub?^d9B*ddJ|-$H8X1^_j3 zPJq(U=;@ttKmuaeYqWq#kxg4CN~skw|$Y`^(^8iNV*el5Vwk^>DaUto2R*)$@PX3usSs z2GnH#*C^DL-~Vf{|J6x(7W|)rD>l0D=8>2A{u5vStH8~ni@%1SSEQFJUJWE;pzmt& zYM_f3S9mqh*#-aAK;DN>_G;kc=%1H0{i$&b2l+fc{}*HcbCds6N``c|}gqO|$s%@@4S%$zvq-E-5GD=zqN z&?`TGK5As^tp$DsD_4GdFQs@u>7`l4{ZqWgAIjtR6SelHqxw$taEW@LqYZ{4|d?ahytmZYx=drdKB=cDNt z_Kdmm^&6umUA{SU+coKH$J+#5i@99iYxmz!YNCI=s$Td%`m=%m*F~wt{>ycW;2@=j zQRodp8l6TVmD6gShSnL;Ch6o1=GMX0@#>oRzrx-BAAV`j|J6y!ZQRYT|G;=DI>V(( z$i?}}!RT+r`v!b_K+>NXS)$%6zZU=A=ER372V%3s`;9KUdS~{K^FCcKcTK$#f8m1k zNZ{bjCwy+6+diRe)8@AZ1ojPD_xtLFZ#I5Wbm{+!E->@ux#!bXkI6i4ll~Mpthr^A zZqGXZ<$L0mCY8@!H+b4%xouF};a=WgI`r*?TV3Cn@YS!S^KKveaK#|6Xs;dV z{>HxkU51Z(7!$`FdbDBV)~n|Rd@*#{!onjv4}V>FJ-~MvW zmp>fwnw;5KquH}8|G7B_AbH`A*{eS_Rko?)qp z{uP?~;r}X41OKm!Qj7gJ%5)5)G0==oO4Bl0p)%^}AeBO4Fa+rov`#5isC0_z_Nqqx zzslYJORknR`2W^P$!+}3TZ!qrdNyx#|Chm+dc=LcVdz!QqM!WN4mB^j*D7Pd;qUhN z`0l;7z4_7uO3xOm@29LWQkzN_7cUw<^;O-F-d#t%F!^91Gcam_t?)|VCnYUbmc1vE z^_#Nbxu~Oq?s$)SQ#VPOGqBVvVeNg#tMputZT1DpM7xTG_M5^bMhnetm!7p!!~||GrWa z{Y&dd|1VWF_@n+}t- z%UJFs8#udZKgY)3W}RJ}@OJT@GfneP>`MFn)s-_7)~vWwcJ2Jli#;Cv?wj2?rO)97 z!#6ECKg-rva^;>~yZnryG^As{wkcOfUU@|mc6xGf%QL6VmQ~rMcYjOUowzf`>xT;$ zqR!lm(>2|6^rJaTEte;4nR>6iWNOLlU(SE8WPbZsKC#BHf4jwrDQCZV=XObWhWCl6 zOMdtLFL#bF`oUA^w+vX-AO zI{kCma(}tV)3EC2=|w(Qn4v9m5A3cq>tQ~MHMqA67tsGSgm;IJuPC= z&Zz7)=a)vl(6r02)3Zb=e;dE3@SPcxH#AvVJiT@F@*?r|n;6T;XGBK* zxaWh@Nk!A=4_1#m{wOd$^0yUlj_$c^)NQY`7UKi$xx4dZiRV1J+=#pW%AUOJ8QncU zYWu;4x%qQS7J7d)x0&shXOYi`&r|YC6W51tE}L`fjYYS{F6$D#C~7+OVX@yKvote$ z_4cbva_*$uRW|D4y)Qid#-(;z(fYJzxgXQd?_Y5&?Ovzo4oxnLw$9D+l>S|@)?3lB zQ)q{Y_x5T&DgT%CbUVlLALmAX+_>GYy@kQux(#fS-sg2gi+AO*Z6fX%k|d5@8wPh9 z_Kz1Pcbt9xTE}S}HCKMkzSHd7jtlcU4J*D`y12vj-)@9%Z|k|%%eXas}pJQLG-@s^vO ze{9}Tw6x!%het17TM`-}_UiE2qLDlG*H5hVJ+^9Qo90{mK21EeC;V=bVXbw)@?5Lu zfA-5gJ7>SqaI)R>&PT^wR1_|tmaO`BV(CHZ`|I9AXKWa~ZKubGkqN1DBpt){Dk*83 zFSZspA1WW)=XzZCy@ilDo#g)`!VmO z9Qgg$U%q>%#pun_`Q9ggNtb56`?`7T{b|R?wlN+Z_e=k-+veFP`+Zdu^!5XtY5$={ zlICBGDUzIvTo&p1*^=p*JM`DL-u|#~TkN}c=dK&E@uk2Q((k60Y-|1dgY#2{OILih zr1jsrUm5no$a&(j?PEimopDex%*)Gm?9?8$KV19O)Pi@=k7_X8aHaa zF0t)*cboQ)4qkpUZO680KgmystrOcB&%L}&)vV=^#>11xADj2XCs8f`o;_~kKgRt( zduJY2)7~%ek|vroD03oH(i-<#%cT^KIu()zXNzp)~F)AgR zXBA~Sbt(;J5y|C!UiZ28I*;8w&fWWc&$B=O?Y;k4YyH0a_x*j>cRVY0j_JOpdU~Y% zpyZzAIg&NA&hDug=%SUjM(eCo`7OZdo|Iv3_J$@C2WLe#`Qdr5qCWZ8T4A!z8yYju zCWZ{rRLkk@``X)_B?klV^?4?qkqCdRoVG8f)huI5&F`y$XtQb^EY9=DfrUPrF`JW5 z6vXc7iJJ8A+`I`dX06=?44%>7x(Jc-FeL&f_IT|IZ_P~zdI-AgqRZ;zQW6Rrw7pgb zfAlz)#m>G@6B@=Ur_6&di6rikZePC6Z(-%( z-(ulo3%tf13Do#~o_5_K@2JyrId?y;**GxxakgZGj@+nGQ)a%j%o}>TiO}f|9WinB z=bq=2wAVjQXo2QLJk;$-M!Q<9FRWa9^qX_$B0b48m7K4`}y%b zHQSc%it4&QBy$aq5c5>9^L0hai*h=B%;m>x-?mNA3P;Bq)dj6OvDIP9spL+>g=tnN zTl9{4iAcwdTjKrTwC=LN=j}B|Jbbdeoo-){l}b_CaDBvNZ1_s~}_of%)! zHQRHi1syylzbrvnaw_mszXqy&q^L5N>XHBCrXTf8uKtyKTmJmD^b|oKs{cmt zvj0dy<^RA!`=1{SL;fs|VJu8R1Ol=!jeX76;UW}6fdB;1I7~Ari39KtuEEp)kAdIi z{|M{<_`^5SxnQ4yEr z3WxwgAfMb zKe)!9(El(j?ElUeeysna2QsSS2R?;}MKwBI+Vn9izc;BdeYVK)Y~|kted9v@5_s)@ zE+e4&FJb*3Ul@k`NeE*B4CitPj3yZ_MY9CNz%WhGD9ge)g0m=>fqrleUi$xQvj6|m z|6hL=>VH1)P5;B}*Zd!GxTw8>>kf~$o8}HLwmaOuTFK5h7*WScOJxGFyv1Y90r3K z3gv<@0b&dQV>HTPNtgq1Ne)T^G|hlNQ~&!1`v1!(q5kIwe?tF14S!XAJgKBb8Tjav zKXxTfzV;A@MY5Gth6MdELjKS2+W!P9Jk- zaXN%ainLUyNC^5-g!~D3*8d2q{#V%lhc666{sayZUo-My3L;sG13)N%KrG9lxHyKB zG)NE#ga1taKLCB_|NruxQ2+CTZ~C7yY#$*a#~ooO=o=EkS9tAzj$rJ6fCCEkKR+0T z{813WAsD4-6z75vhQb&{;v`H17|v24NKgO;;y>yN&eQ(qVBg#S9HIW_2jBcZ2tfbO z0$|_Z?t8wlOXCAwy{DO#C*N0Vf65G(k(H@1F;R9iH&)c|ely42T}_R(pFe9t^l9C; zrXJgl$U!(-jCBZ9vp90hvSOvi+{>pv7yXu2wMDv4#8- zUi*JDNxS^)3GgTMKZ*$Lf4;y={)~Y9e~2*ui!TgA{sfnS2#SKZAeV!{2!&G+K{6PI zl3bjI7zC$LnE0vwAK-WAKgFu2)^BH>9^{esfB9OiD_$%y;)?MOqgfxySuInJ_f%j zw(jk3#y$qtH>B&H37NVxQ?F3ual0q`A$&_{=$)s@&t%VC(ECv+QOI8m&-@=j_5Tz4 z|MkB<9z3d;E zct76(&eM~9Tmj@6n7(zs;q>_hdUAzPVe9dCDSj75=xqM=zR{ho&iR1{9-p~n&;2i;{!haBU;JSh@@H5MN#Hb&GaycKC=SF>AO>P2gHc~z0w9(P zaS-HZ{67%#UHl&s&j02QU+4d{$n92FSn{#gsyFwbh+FL~WTFKaNb`LX`H>x*y7&WKWq zPZ-#uJb(1q*D8S=OY20LeMgO}CRjzy>dn~qW`RFkIno5pK`SOp2M15RTG0Bb6R;eb zRH+*J^m?pAUh1N_x*Mtf4>bHl#@yAj74!oA&EeVqK``|nAS~?v$`^(qe~M-iiokFL zB}f#dzm98Qa4rCXIKU7z#vlZTz>ptYgQxxf2lIabVf`Op`2U{&b4J`}*0L#mDs#|B zcbTGx`DSiKw{_2%%^N7am%P*|hDestmLByPXT5VLmudMNF1Sg(b1MJPv(jRAkY>~I zz;Ozs2!`Y$AdPVW3;}+o{u}zf{|6xS|MQ2h_J8Hq{@;PV{pMYh0WI&(GV$#eIax=q z6mPRG-9HF%!@g=aSbc>>J4eCa-b{F;;>apm3jYuL}qtgw5|3uam?|>ylpz0 zu8nVxLtjN}t}Cv{Tv8Vxt>iLiS9JH1THoIH8J&%eo#M~3&fliO)9K6gU7dYOz^%it zcF1gYZ?~P<1lLP+*1QwFAiuEilj3FL1v5AgnMRA8sLtGJGd?`;8miGcD^YjElpOG$ zkbjUe&Jgs%{Y~Jt|D86mu7b$_68iu71JC*o0rmevh4FvBFbw%a3<*LA3v!^Zo#4N` ztN}O*k~G3_QHDZ+FE51@#Qs$M&mW!ti17U92S4urFzOBuc{s8 zqW#8*c&-~G4ATES;5q*R5RCpug!TV?VHol!2rfi}AQuHW2!s(hOEMIP3&11+umldk zUycBZ`!n$$82Env2O_Nh7so>++nb+*=C34`140<9Wp&NOjPeBIYzaO zJh}TnRJ-qbE>_lk<7RKHnPXD&*e7RP?42C%5ckqY{font-rt)2uyCc^;QIEL(V`M- zeZr+4!5?S0)+%W^_?!8dCnu$-G~Mn_M2)JVEogrxzviMvmfM)wmMb(Z4T8%S(JKLhAC8hHG zzLWzp(YGdK9PA6M%zxgjCRIDR&iK}K@5y@A6~^}E6BARpZ#7$0R;l%q28!w)k2Uq5 zghj>6Iv>kErfy+*?27B?OTXr27b=_yR&ts3#Aik1fQ5e8QO(`2bosj}xbq$^ z7#RrXZe0~=-Y4pBTRD2h8;_bh5iu8QB>XQn#7YGW*h!Nrrx1h7s4#B~N-GTM{|jPjCH{#Yw5`<%^0u&x&htdN$@Xr`6BWvL1V5la|!$ zVhsoF?CY&Hj?K?IOX6Dk7A~Nk#Yi?4%rUeH{WQ4VIlj$+(_DxLnail0bpFsR7I-?Z zE3ZjJeEZsGNnH;&URKzs)Z46kpHn+%g@7v2>zC!~OOrA~aN-nLQ*j*+s zH(jbbbh1aXy|w#mZ8@LnPmdOCPK(AJTAO>LUzkHDMxWSQ>o@O%1OX10K{yl)BLGS;D8j)h45cXmA+eu`|A7ed-TYq= z62AZO1uxMP7N+6%|Lgo$oHK<0%0CIy0i}&t3pxYoaHQo4HAHHaW)lnnWz8j&V;)C9+ zYlW$te<6QwGH7_}nax8(6>3e#$;^1KGT+gIx8&Cq8CE8!+xp%tTRU=Ah@sly=`VNV zcE#o1s^SUa=<}Gj51I|~8QGk<((n`;#cZp%smGfR*DWemRU9#6+KQEab3$!4Nj|!{ zsp?tMmRCu4d$*Ob2%6QC%rGyu083$hB{_nKBS;=w>)Gd-hGf`ABy5tJgU2u@FT4~)!#b2y+; z%Xq+wM-bNtL|sMH0V5v77>oxhl~RrXE<+LtsH-f9iMPfj>dwGm0xpN5!=~zeRa390 ztNZ_7zyIB@`+wKHQ;D6LWLduzB{!z9lcqi27x;%Az$SET-@iu{R(+3b|5P)JxSzOQ*Cs36c+K$tk6nn)|L3EAHwE2pQEYI ztLroKZ+3SneS36N|4W%}+W!HJ|E)qN>`zfrOiJ*thcj=YTqH$dheb6yn__6Li-Vm7&EMX{V-bP+yo; zEaCy8gJP#mo8?nL|p)8YDm_tgKu|BsdEg#9T##%F{A45e9w zMp#6^6EYHtCWJVN@DZA(aE8IU;{SnsdHxqC!22IoA-2bc*?G#e_f6x!>>(LKBA&N+ z$)Pn{LN~4o??;bbKk-J+JaO+Z+r+K;hOFfu#q&fGPJBcOcE9TD6te$dZ0!Z+mpXi0 z#82}a9;JI-d|Yv^lo6bIws7v!y>^Lzt`ZD7b2hPdlGDDV6R~P>fo}88-SwPs`v?1m zFVTDu9#wuXagI_`@1Rf1Xjq#STb4QfR@|n!#TDA8SEiJGar&#Ue$Y?!^v&ra&kqrW+JLR{)oGjE4y2C zWAKjaWdoLMIJaQNyx#8)O}bkiM@kXc&Vk&M+~?u1<5SCIr;WSlA)j5UKX&1He7su- zGW@3l$g!lVt0BV@r!F>DCMZtrnOZwPRFp@k9!z#E5se#mWMHgN5X9-}5xopHoGFSn zI1hQR3UUn`{wb#Dea|KLewv|||G9tpFP=CJIe3T}RTuqjP(H z%0Il~!tuikR(nb=ubXn&<63o6jAoMSoL}Ez>z$KZ*uSIlPf*=7{|CPRwF;fEKcB{g z6vaw#R6x@xkDxIgpGPAMNn)r#!1757rP!|Q|NKAvzhZo_|EE=G)BkI6a$J5ky~fpu zR;+%CT?!wRuMt(1oH@HsXTkDxvt1Z$hxI zL6+Z(b5`B(#M5nR!w>cPk4kw3+{laTJZ&H()$QBver$k+4)=fQp7kFL1@*sG=!E@Q z29>fT!D3Q`zyt(}VmOYon2$$@b%6%TB>h47R1JhRdmnj)G z_6_MUmvMeO>%TmH>-wL-!2a*n!N-5Lw<)kO{T#z({YN-J?AhiJ>S1dGF@60ZXyqOV zvaN0U_3mpQjVvTLjNN|1_1WqtgZmD3u&=0woXpuq7O7OJUu*@@DCgN1KR1UKc|k4Z z*~To}JNEmJM_f5`WzF{7^T{#d!t@`rwUp=h z$q3guo(;SHR5kr5VZS3J>dX0u0@RR~yP8!->pYq>y9XboS(@YvxERStxdhD;T%kZ} z`YXXO2@fYE3`uz?SS@9~qcI9?q)$Lg{XJ$vDP?(o31(ck{ij}GYjD|S8 zA}^|?Jjf&rBSD zP(Saxv)9+pNrL*Q*+n{;npLn$t;Ss&YkJa<*}mpi!4n`Z3ZqU<>0&;{oXm*>nNMo_ zk38*iC%vdXpkaa9D9kBbthw8qXr>Dgrv&%!M&%m|pB+iwk&t^!8W@V_O@DDgqukEq z8}pTjljLfptRlWV@SnYx`6aL2>V4kmmtc)D{VKFo|I1Ce=}qZ)_5MFjg71H=LtFNr z;p^q|rgU`B{+k^@w6;GV1O9)kf~jVl1UI%FQ7pxn8V*XMWz|mI9W8*BXuJR4b-xeI zyn6jlpg36nwF>RppN*yEI;Op?-8w}_^*{d7_>ad!fd6N!Fa|bR8DO4A+MC`t>P(@+cRW!fm%b&M-asacp-DVn1E1DSg+E_8Cb%au8vfx7}%66VU^S* z!J-w8p|qyvNvVOy>68`?W)TWlCWYfvI#^GcPpDv>hHX4Qev;|t3GQJkD%^aq95igL zXsi4;b_w2)j@PRHf&VwF(60TpROFk;1h29FgOFhT$11dI|E3{9mvy|(`X2`Re^#Lr z_HP;zc0osn<9}W&|4%5Nz`^%lRzXUkC@x?y3Cm+~oWxNCM<_~&NhpHhqYNWOD2&hG tQqXY%0000000000000000000000000000000O0?Z{{lS_s&@d80RXR<#z6o8 literal 0 HcmV?d00001 diff --git a/tests/test_cli.py b/tests/test_cli.py index 2aedeb3f4a..79b42b7040 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -413,41 +413,6 @@ def test_file_tracking(isolated_runner): assert 'output' in Path('.gitattributes').read_text() -def test_status_with_old_repository(isolated_runner, old_project): - """Test status on all old repositories created by old version of renku.""" - runner = isolated_runner - - result = runner.invoke(cli.cli, ['status']) - assert 0 == result.exit_code - - output = result.output.split('\n') - assert output.pop(0) == 'On branch master' - assert output.pop(0) == 'All files were generated from the latest inputs.' - - -def test_update_with_old_repository(isolated_runner, old_project): - """Test update on all old repositories created by old version of renku.""" - runner = isolated_runner - - result = runner.invoke(cli.cli, ['update']) - assert 0 == result.exit_code - - output = result.output.split('\n') - assert output.pop(0) == 'All files were generated from the latest inputs.' - - -def test_list_with_old_repository(isolated_runner, old_project): - """Test dataset list on old repository.""" - result = isolated_runner.invoke(cli.cli, ['dataset']) - assert 0 == result.exit_code - - -def test_migrate_with_old_repository(isolated_runner, old_project): - """Test migrate on old repository.""" - result = isolated_runner.invoke(cli.cli, ['migrate', 'datasets']) - assert 0 == result.exit_code - - def test_status_with_submodules(isolated_runner, monkeypatch): """Test status calculation with submodules.""" runner = isolated_runner