diff --git a/renku/core/commands/client.py b/renku/core/commands/client.py index 12d103fae0..33eec09039 100644 --- a/renku/core/commands/client.py +++ b/renku/core/commands/client.py @@ -26,6 +26,8 @@ from renku.core.management import LocalClient +from ..management.config import RENKU_HOME +from ..management.repository import default_path from .git import get_git_isolation @@ -63,8 +65,17 @@ def pass_local_client( ) def new_func(*args, **kwargs): - ctx = click.get_current_context() - client = ctx.ensure_object(LocalClient) + ctx = click.get_current_context(silent=True) + if ctx is None: + client = LocalClient( + path=default_path(), + renku_home=RENKU_HOME, + use_external_storage=True, + ) + ctx = click.Context(click.Command(method)) + else: + client = ctx.ensure_object(LocalClient) + stack = contextlib.ExitStack() # Handle --isolation option: @@ -73,20 +84,24 @@ def new_func(*args, **kwargs): transaction = client.transaction( clean=clean, - up_to_date=up_to_date, commit=commit, + commit_empty=commit_empty, + commit_message=kwargs.get('commit_message', None), commit_only=commit_only, ignore_std_streams=ignore_std_streams, - commit_empty=commit_empty, raise_if_empty=raise_if_empty, + up_to_date=up_to_date, ) stack.enter_context(transaction) if lock or (lock is None and commit): stack.enter_context(client.lock) - with stack: - result = ctx.invoke(method, client, *args, **kwargs) + result = None + if ctx: + with stack: + result = ctx.invoke(method, client, *args, **kwargs) + return result return functools.update_wrapper(new_func, method) diff --git a/renku/core/commands/clone.py b/renku/core/commands/clone.py index d3805640a5..b7f098416f 100644 --- a/renku/core/commands/clone.py +++ b/renku/core/commands/clone.py @@ -29,7 +29,8 @@ def renku_clone( path=None, install_githooks=True, skip_smudge=True, - progress=None + progress=None, + commit_message=None ): """Clone Renku project repo, install Git hooks and LFS.""" install_lfs = client.use_external_storage diff --git a/renku/core/commands/config.py b/renku/core/commands/config.py index 9121989b6f..efc8b9823e 100644 --- a/renku/core/commands/config.py +++ b/renku/core/commands/config.py @@ -36,7 +36,15 @@ def _split_section_and_key(key): commit_only=CONFIG_LOCAL_PATH, commit_empty=False ) -def update_config(client, key, *, value=None, remove=False, global_only=False): +def update_config( + client, + key, + *, + value=None, + remove=False, + global_only=False, + commit_message=None +): """Add, update, or remove configuration values.""" section, section_key = _split_section_and_key(key) if remove: diff --git a/renku/core/commands/dataset.py b/renku/core/commands/dataset.py index 6604b2838a..51e78fc256 100644 --- a/renku/core/commands/dataset.py +++ b/renku/core/commands/dataset.py @@ -101,7 +101,7 @@ def dataset_parent(client, revision, datadir, format, ctx=None): @pass_local_client( clean=False, commit=True, commit_only=DATASET_METADATA_PATHS ) -def create_dataset(client, name): +def create_dataset(client, name, commit_message=None): """Create an empty dataset in the current repo. :raises: ``renku.core.errors.ParameterError`` @@ -115,7 +115,7 @@ def create_dataset(client, name): @pass_local_client( clean=False, commit=True, commit_only=DATASET_METADATA_PATHS ) -def edit_dataset(client, dataset_id, transform_fn): +def edit_dataset(client, dataset_id, transform_fn, commit_message=None): """Edit dataset metadata.""" dataset = client.load_dataset(dataset_id) @@ -146,7 +146,8 @@ def add_file( destination='', ref=None, with_metadata=None, - urlscontext=contextlib.nullcontext + urlscontext=contextlib.nullcontext, + commit_message=None, ): """Add data file to a dataset.""" add_to_dataset( @@ -166,7 +167,8 @@ def add_to_dataset( destination='', ref=None, with_metadata=None, - urlscontext=contextlib.nullcontext + urlscontext=contextlib.nullcontext, + commit_message=None, ): """Add data to a dataset.""" if len(urls) == 0: @@ -247,7 +249,7 @@ def list_files(client, names, creators, include, exclude, format): commit_only=COMMIT_DIFF_STRATEGY, ) @contextmanager -def file_unlink(client, name, include, exclude): +def file_unlink(client, name, include, exclude, commit_message=None): """Remove matching files from a dataset.""" dataset = client.load_dataset(name=name) @@ -278,7 +280,8 @@ def dataset_remove( names, with_output=False, datasetscontext=contextlib.nullcontext, - referencescontext=contextlib.nullcontext + referencescontext=contextlib.nullcontext, + commit_message=None ): """Delete a dataset.""" datasets = {name: client.dataset_path(name) for name in names} @@ -338,7 +341,8 @@ def export_dataset( publish, tag, handle_access_token_fn=None, - handle_tag_selection_fn=None + handle_tag_selection_fn=None, + commit_message=None, ): """Export data to 3rd party provider. @@ -423,7 +427,8 @@ def import_dataset( with_prompt=False, pool_init_fn=None, pool_init_args=None, - download_file_fn=default_download_file + download_file_fn=default_download_file, + commit_message=None, ): """Import data from a 3rd party provider.""" provider, err = ProviderFactory.from_uri(uri) @@ -538,7 +543,8 @@ def update_datasets( exclude, ref, delete, - progress_context=contextlib.nullcontext + progress_context=contextlib.nullcontext, + commit_message=None, ): """Update files from a remote Git repo.""" records = _filter( @@ -650,7 +656,9 @@ def _filter(client, names=None, creators=None, include=None, exclude=None): commit=True, commit_only=COMMIT_DIFF_STRATEGY, ) -def tag_dataset_with_client(client, name, tag, description, force=False): +def tag_dataset_with_client( + client, name, tag, description, force=False, commit_message=None +): """Creates a new tag for a dataset and injects a LocalClient.""" tag_dataset(client, name, tag, description, force) @@ -674,7 +682,7 @@ def tag_dataset(client, name, tag, description, force=False): commit=True, commit_only=COMMIT_DIFF_STRATEGY, ) -def remove_dataset_tags(client, name, tags): +def remove_dataset_tags(client, name, tags, commit_message=True): """Removes tags from a dataset.""" dataset = client.load_dataset(name) if not dataset: diff --git a/renku/core/commands/migrate.py b/renku/core/commands/migrate.py index 7f5e419569..dfaa8e5f77 100644 --- a/renku/core/commands/migrate.py +++ b/renku/core/commands/migrate.py @@ -24,7 +24,10 @@ @pass_local_client( clean=True, commit=True, commit_empty=False, raise_if_empty=True ) -def migrate_datasets(client): +def migrate_datasets( + client, + commit_message=None, +): """Migrate dataset metadata.""" results = [ migration(client) is not False for migration in STRUCTURE_MIGRATIONS diff --git a/renku/core/errors.py b/renku/core/errors.py index 92cfe46b36..3b143a8671 100644 --- a/renku/core/errors.py +++ b/renku/core/errors.py @@ -198,6 +198,14 @@ def __init__(self): super(NothingToCommit, self).__init__('There is nothing to commit.') +class CommitMessageEmpty(RenkuException): + """Raise invalid commit message.""" + + def __init__(self): + """Build a custom message.""" + super(CommitMessageEmpty, self).__init__('Invalid commit message.') + + class FailedMerge(RenkuException): """Raise when automatic merge failed.""" diff --git a/renku/core/management/git.py b/renku/core/management/git.py index 43313e9b43..ecaae33c80 100644 --- a/renku/core/management/git.py +++ b/renku/core/management/git.py @@ -230,7 +230,11 @@ def ensure_unstaged(self, path): @contextmanager def commit( - self, commit_only=None, commit_empty=True, raise_if_empty=False + self, + commit_only=None, + commit_empty=True, + raise_if_empty=False, + commit_message=None ): """Automatic commit.""" from git import Actor @@ -302,12 +306,18 @@ def commit( raise errors.NothingToCommit() return - argv = [os.path.basename(sys.argv[0]) - ] + [remove_credentials(arg) for arg in sys.argv[1:]] + if commit_message and not isinstance(commit_message, str): + raise errors.CommitMessageEmpty() + + elif not commit_message: + argv = [os.path.basename(sys.argv[0]) + ] + [remove_credentials(arg) for arg in sys.argv[1:]] + + commit_message = ' '.join(argv) # Ignore pre-commit hooks since we have already done everything. self.repo.index.commit( - ' '.join(argv), + commit_message, committer=committer, skip_hooks=True, ) @@ -316,12 +326,13 @@ def commit( def transaction( self, clean=True, - up_to_date=False, commit=True, + commit_empty=True, + commit_message=None, commit_only=None, ignore_std_streams=False, - commit_empty=True, - raise_if_empty=False + raise_if_empty=False, + up_to_date=False, ): """Perform Git checks and operations.""" if clean: @@ -335,8 +346,9 @@ def transaction( if commit: with self.commit( - commit_only=commit_only, commit_empty=commit_empty, + commit_message=commit_message, + commit_only=commit_only, raise_if_empty=raise_if_empty ): yield self diff --git a/tests/core/commands/test_dataset.py b/tests/core/commands/test_dataset.py index 24a64f6ee2..481b054738 100644 --- a/tests/core/commands/test_dataset.py +++ b/tests/core/commands/test_dataset.py @@ -23,8 +23,10 @@ from contextlib import contextmanager import pytest +from git import Repo from renku.core import errors +from renku.core.commands.dataset import create_dataset from renku.core.models.datasets import Dataset, DatasetFile from renku.core.models.provenance.agents import Person @@ -174,3 +176,10 @@ def test_dataset_serialization(dataset): assert dataset.identifier == dataset_metadata.get('identifier') assert dataset.name == dataset_metadata.get('name') assert dataset.path == dataset_metadata.get('path') + + +def test_create_dataset_custom_message(project): + """Test create dataset custom message.""" + create_dataset('ds1', commit_message='my awesome dataset') + last_commit = Repo('.').head.commit + assert 'my awesome dataset' == last_commit.message