diff --git a/datalad/cli/common_args.py b/datalad/cli/common_args.py index 6b1d4b3f6c..eeae343620 100644 --- a/datalad/cli/common_args.py +++ b/datalad/cli/common_args.py @@ -34,10 +34,12 @@ ('-c',), dict(action='append', dest='cfg_overrides', - metavar='KEY=VALUE', - help="""configuration variable setting. Overrides any - configuration read from a file, but is potentially overridden - itself by configuration variables in the process environment. + metavar='(:name|name=value)', + help="""specify configuration setting overrides. They override any + configuration read from a file. A configuration can also be + unset temporarily by prefixing its name with a colon (':'), e.g. ':user.name'. + Overrides specified here may be overridden themselves by + configuration settings declared as environment variables. """)), change_path=( ('-C',), diff --git a/datalad/cli/helpers.py b/datalad/cli/helpers.py index b657b65581..c18691ac0a 100644 --- a/datalad/cli/helpers.py +++ b/datalad/cli/helpers.py @@ -255,17 +255,23 @@ def _parse_overrides_from_cmdline(cmdlineargs): # errors: we need a section, a variable, and a value at minimum # otherwise we break our own config parsing helpers # https://github.com/datalad/datalad/issues/3451 - noassign_expr = re.compile(r'[^\s]+\.[^\s]+=[\S]+') + assign_expr = re.compile(r'[^\s]+\.[^\s]+=[\S]+') + unset_expr = re.compile(r':[^\s]+\.[^\s=]+') noassign = [ o for o in cmdlineargs.cfg_overrides - if not noassign_expr.match(o) + if not (assign_expr.match(o) or unset_expr.match(o)) ] if noassign: lgr.error( "Configuration override without section/variable " - "or value assignment (must be 'section.variable=value'): %s", + "or unset marker or value assignment " + "(must be '(:section.variable|section.variable=value)'): %s", noassign) sys.exit(3) - overrides = dict(o.split('=', 1) for o in cmdlineargs.cfg_overrides) + overrides = dict( + [o[1:], None] if o.startswith(':') + else o.split('=', 1) + for o in cmdlineargs.cfg_overrides + ) return overrides diff --git a/datalad/cli/tests/test_main.py b/datalad/cli/tests/test_main.py index 3cd55bc146..5d5cba5f89 100644 --- a/datalad/cli/tests/test_main.py +++ b/datalad/cli/tests/test_main.py @@ -269,11 +269,12 @@ def test_script_shims(script): get_numeric_portion(version)) -@slow # 11.2591s @with_tempfile(mkdir=True) def test_cfg_override(path=None): with chpwd(path): - cmd = ['datalad', 'wtf', '-s', 'some'] + # use 'wtf' to dump the config + # should be rewritten to use `configuration` + cmd = ['datalad', 'wtf', '-S', 'configuration', '-s', 'some'] # control out = Runner().run(cmd, protocol=StdOutErrCapture)['stdout'] assert_not_in('datalad.dummy: this', out) @@ -305,6 +306,27 @@ def test_cfg_override(path=None): protocol=StdOutErrCapture)['stdout'] assert_in('datalad.dummy: this', out) + # set a config + run_main([ + 'configuration', '--scope', 'local', 'set', 'mike.item=some']) + # verify it is successfully set + assert 'some' == run_main([ + 'configuration', 'get', 'mike.item'])[0].strip() + # verify that an override can unset the config + # we cannot use run_main(), because the "singleton" instance of the + # dataset we are in is still around in this session, and with it + # also its config managers that we will not be able to post-hoc + # overwrite with this method. Instead, we'll execute in a subprocess. + assert '' == Runner().run([ + 'datalad', '-c', ':mike.item', + 'configuration', 'get', 'mike.item'], + protocol=StdOutErrCapture)['stdout'].strip() + # verify the effect is not permanent + assert 'some' == Runner().run([ + 'datalad', + 'configuration', 'get', 'mike.item'], + protocol=StdOutErrCapture)['stdout'].strip() + def test_incorrect_cfg_override(): run_main(['-c', 'some', 'wtf'], exit_code=3)