diff --git a/dvc/command/config.py b/dvc/command/config.py index ee2c07e430..70deb90f50 100644 --- a/dvc/command/config.py +++ b/dvc/command/config.py @@ -1,8 +1,10 @@ import argparse import logging +import os from dvc.command.base import CmdBaseNoRepo, append_doc_link from dvc.config import Config, ConfigError +from dvc.repo import Repo from dvc.utils.flatten import flatten logger = logging.getLogger(__name__) @@ -34,6 +36,14 @@ def __init__(self, args): self.config = Config(validate=False) def run(self): + if self.args.show_origin: + if any((self.args.value, self.args.unset)): + logger.error( + "--show-origin can't be used together with any of these " + "options: -u/--unset, value" + ) + return 1 + if self.args.list: if any((self.args.name, self.args.value, self.args.unset)): logger.error( @@ -43,7 +53,11 @@ def run(self): return 1 conf = self.config.load_one(self.args.level) - logger.info("\n".join(self._format_config(conf))) + prefix = self._config_file_prefix( + self.args.show_origin, self.config, self.args.level + ) + + logger.info("\n".join(self._format_config(conf, prefix))) return 0 if self.args.name is None: @@ -54,10 +68,14 @@ def run(self): if self.args.value is None and not self.args.unset: conf = self.config.load_one(self.args.level) + prefix = self._config_file_prefix( + self.args.show_origin, self.config, self.args.level + ) + if remote: conf = conf["remote"] self._check(conf, remote, section, opt) - logger.info(conf[section][opt]) + logger.info("{}{}".format(prefix, conf[section][opt])) return 0 with self.config.edit(self.args.level) as conf: @@ -90,9 +108,21 @@ def _check(self, conf, remote, section, opt=None): ) @staticmethod - def _format_config(config): + def _format_config(config, prefix=""): for key, value in flatten(config).items(): - yield f"{key}={value}" + yield f"{prefix}{key}={value}" + + @staticmethod + def _config_file_prefix(show_origin, config, level): + if not show_origin: + return "" + + fname = config.files[level] + + if level in ["local", "repo"]: + fname = os.path.relpath(fname, start=Repo.find_root()) + + return fname + "\t" parent_config_parser = argparse.ArgumentParser(add_help=False) @@ -152,4 +182,10 @@ def add_parser(subparsers, parent_parser): action="store_true", help="List all defined config values.", ) + config_parser.add_argument( + "--show-origin", + default=False, + action="store_true", + help="Show the source file containing each config value.", + ) config_parser.set_defaults(func=CmdConfig) diff --git a/tests/func/test_config.py b/tests/func/test_config.py index 0e0d6bb01d..a208cbcdaf 100644 --- a/tests/func/test_config.py +++ b/tests/func/test_config.py @@ -48,9 +48,15 @@ def _do_test(self, local=False): self.assertEqual(ret, 0) self.assertTrue(self._contains(section, field, value, local)) + ret = main(base + [section_field, value, "--show-origin"]) + self.assertEqual(ret, 1) + ret = main(base + [section_field]) self.assertEqual(ret, 0) + ret = main(base + ["--show-origin", section_field]) + self.assertEqual(ret, 0) + ret = main(base + [section_field, newvalue]) self.assertEqual(ret, 0) self.assertTrue(self._contains(section, field, newvalue, local)) @@ -60,9 +66,15 @@ def _do_test(self, local=False): self.assertEqual(ret, 0) self.assertFalse(self._contains(section, field, value, local)) + ret = main(base + [section_field, "--unset", "--show-origin"]) + self.assertEqual(ret, 1) + ret = main(base + ["--list"]) self.assertEqual(ret, 0) + ret = main(base + ["--list", "--show-origin"]) + self.assertEqual(ret, 0) + def test(self): self._do_test(False) @@ -174,3 +186,28 @@ def test_config_remote(tmp_dir, dvc, caplog): caplog.clear() assert main(["config", "remote.myremote.region"]) == 0 assert "myregion" in caplog.text + + +def test_config_show_origin(tmp_dir, dvc, caplog): + (tmp_dir / ".dvc" / "config").write_text( + "['remote \"myremote\"']\n" + " url = s3://bucket/path\n" + " region = myregion\n" + ) + + caplog.clear() + assert main(["config", "--show-origin", "remote.myremote.url"]) == 0 + assert ( + "{}\t{}\n".format(os.path.join(".dvc", "config"), "s3://bucket/path") + in caplog.text + ) + + caplog.clear() + assert main(["config", "--list", "--show-origin"]) == 0 + assert ( + "{}\t{}\n".format( + os.path.join(".dvc", "config"), + "remote.myremote.url=s3://bucket/path", + ) + in caplog.text + )