Repository: borgbackup/borg

Issue:
List of all repository options and their values
`borg config` allows to get and set options for a repository, provided you know the option's name. Unfortunately there's no way to get a list of all available options on the command line.  Even in the documentation they aren't listed in a single place.  There is no way besides reading through all of the documentation to find out the possible repository options.

Therefore there should be an option to `borg config` that lists all repository options and their values similar to `git config --list`, for example.


Discussion:
Shell tab completions could also benefit from this, so 👍 
First Timer here. Could I try and solve this?
This is open source. If you have the time and the knowledge, do it, and send a pull request :smile: 
I closed a duplicate of this issue, see #3671 for more discussion.
Since argparse is kind of stupid (patched in python 3.7 but not backward compatible)
the only solution I see is to allow
`borg config --list <repo>` and `borg config --delete <repo> <name>` but not `borg config <repo> --delete <name>`
This is because argparse does not handles well mixed optional and positional arguments.
Thus, it would need to modify a little the test suite.

Is it a problem if I do so ?
I have a patch ready for this.
@Gu1nness we already documented that order matters here:
http://borgbackup.readthedocs.io/en/stable/usage/general.html#positional-arguments-and-options-order-matters

If we did not implement that everywhere, please fix it.
Ok perfect.
Then, I'll change [this test](https://github.com/borgbackup/borg/blob/master/src/borg/testsuite/archiver.py#L2794) also in order to be compliant with the documentation.

Resolving patch:
diff --git a/src/borg/archiver.py b/src/borg/archiver.py
--- a/src/borg/archiver.py
+++ b/src/borg/archiver.py
@@ -1775,11 +1775,32 @@ def cache_validate(section, name, value=None, check_value=True):
             else:
                 raise ValueError('Invalid name')
 
-        try:
-            section, name = args.name.split('.')
-        except ValueError:
-            section = args.cache and "cache" or "repository"
-            name = args.name
+        def list_config(config):
+            default_values = {
+                'version': '1',
+                'segments_per_dir': str(DEFAULT_SEGMENTS_PER_DIR),
+                'max_segment_size': str(MAX_SEGMENT_SIZE_LIMIT),
+                'additional_free_space': '0',
+                'storage_quota': repository.storage_quota,
+                'append_only': repository.append_only
+            }
+            print('[repository]')
+            for key in ['version', 'segments_per_dir', 'max_segment_size',
+                        'storage_quota', 'additional_free_space', 'append_only',
+                        'id']:
+                value = config.get('repository', key, fallback=False)
+                if value is None:
+                    value = default_values.get(key)
+                    if value is None:
+                        raise Error('The repository config is missing the %s key which has no default value' % key)
+                print('%s = %s' % (key, value))
+
+        if not args.list:
+            try:
+                section, name = args.name.split('.')
+            except ValueError:
+                section = args.cache and "cache" or "repository"
+                name = args.name
 
         if args.cache:
             manifest, key = Manifest.load(repository, (Manifest.Operation.WRITE,))
@@ -1803,6 +1824,8 @@ def cache_validate(section, name, value=None, check_value=True):
                 if len(config.options(section)) == 0:
                     config.remove_section(section)
                 save()
+            elif args.list:
+                list_config(config)
             elif args.value:
                 validate(section, name, args.value)
                 if section not in config.sections():
@@ -3846,10 +3869,13 @@ def define_archive_filters_group(subparser, *, sort_by=True, first_last=True):
         This command gets and sets options in a local repository or cache config file.
         For security reasons, this command only works on local repositories.
 
-        To delete a config value entirely, use ``--delete``. To get an existing key, pass
-        only the key name. To set a key, pass both the key name and the new value. Keys
-        can be specified in the format "section.name" or simply "name"; the section will
-        default to "repository" and "cache" for the repo and cache configs, respectively.
+        To delete a config value entirely, use ``--delete``. To list the values
+        of the configuration file or the default values, use ``--list``.  To get and existing
+        key, pass only the key name. To set a key, pass both the key name and
+        the new value. Keys can be specified in the format "section.name" or
+        simply "name"; the section will default to "repository" and "cache" for
+        the repo and cache configs, respectively.
+
 
         By default, borg config manipulates the repository config file. Using ``--cache``
         edits the repository cache's config file instead.
@@ -3862,13 +3888,17 @@ def define_archive_filters_group(subparser, *, sort_by=True, first_last=True):
         subparser.set_defaults(func=self.do_config)
         subparser.add_argument('-c', '--cache', dest='cache', action='store_true',
                                help='get and set values from the repo cache')
-        subparser.add_argument('-d', '--delete', dest='delete', action='store_true',
+
+        group = subparser.add_mutually_exclusive_group()
+        group.add_argument('-d', '--delete', dest='delete', action='store_true',
                                help='delete the key from the config file')
+        group.add_argument('-l', '--list', dest='list', action='store_true',
+                               help='list the configuration of the repo')
 
         subparser.add_argument('location', metavar='REPOSITORY',
                                type=location_validator(archive=False, proto='file'),
                                help='repository to configure')
-        subparser.add_argument('name', metavar='NAME',
+        subparser.add_argument('name', metavar='NAME', nargs='?',
                                help='name of config key')
         subparser.add_argument('value', metavar='VALUE', nargs='?',
                                help='new value for key')
diff --git a/src/borg/testsuite/archiver.py b/src/borg/testsuite/archiver.py
--- a/src/borg/testsuite/archiver.py
+++ b/src/borg/testsuite/archiver.py
@@ -2785,6 +2785,14 @@ def test_config(self):
         self.create_test_files()
         os.unlink('input/flagfile')
         self.cmd('init', '--encryption=repokey', self.repository_location)
+        output = self.cmd('config', '--list', self.repository_location)
+        self.assert_in('[repository]', output)
+        self.assert_in('version', output)
+        self.assert_in('segments_per_dir', output)
+        self.assert_in('storage_quota', output)
+        self.assert_in('append_only', output)
+        self.assert_in('additional_free_space', output)
+        self.assert_in('id', output)
         for cfg_key, cfg_value in [
             ('additional_free_space', '2G'),
             ('repository.append_only', '1'),
@@ -2794,8 +2802,9 @@ def test_config(self):
             self.cmd('config', self.repository_location, cfg_key, cfg_value)
             output = self.cmd('config', self.repository_location, cfg_key)
             assert output == cfg_value + '\n'
-            self.cmd('config', self.repository_location, '--delete', cfg_key)
+            self.cmd('config', '--delete', self.repository_location, cfg_key)
             self.cmd('config', self.repository_location, cfg_key, exit_code=1)
+        self.cmd('config', '--list', '--delete', self.repository_location, exit_code=2)
 
     requires_gnutar = pytest.mark.skipif(not have_gnutar(), reason='GNU tar must be installed for this test.')
     requires_gzip = pytest.mark.skipif(not shutil.which('gzip'), reason='gzip must be installed for this test.')


Test patch: