Browse Source

Merge pull request #2748 from enkore/f/cleanup-argparse2

archiver: more argparse cleanup, redundant options, missing metavars
enkore 8 years ago
parent
commit
12fd244801
2 changed files with 131 additions and 240 deletions
  1. 4 1
      setup.py
  2. 127 239
      src/borg/archiver.py

+ 4 - 1
setup.py

@@ -317,7 +317,10 @@ class build_usage(Command):
             else:
             else:
                 if not group._group_actions:
                 if not group._group_actions:
                     continue
                     continue
-                rows.append((1, '**%s**' % group.title))
+                group_header = '**%s**' % group.title
+                if group.description:
+                    group_header += ' — ' + group.description
+                rows.append((1, group_header))
                 if is_positional_group(group):
                 if is_positional_group(group):
                     for option in group._group_actions:
                     for option in group._group_actions:
                         rows.append((3, '', '``%s``' % option.metavar, option.help or ''))
                         rows.append((3, '', '``%s``' % option.metavar, option.help or ''))

+ 127 - 239
src/borg/archiver.py

@@ -2304,35 +2304,91 @@ class Archiver:
             add_common_option('--debug', dest='log_level',
             add_common_option('--debug', dest='log_level',
                               action='store_const', const='debug', default='warning',
                               action='store_const', const='debug', default='warning',
                               help='enable debug output, work on log level DEBUG')
                               help='enable debug output, work on log level DEBUG')
-            add_common_option('--debug-topic', dest='debug_topics',
-                              action='append', metavar='TOPIC', default=[],
+            add_common_option('--debug-topic', metavar='TOPIC', dest='debug_topics', action='append', default=[],
                               help='enable TOPIC debugging (can be specified multiple times). '
                               help='enable TOPIC debugging (can be specified multiple times). '
                                    'The logger path is borg.debug.<TOPIC> if TOPIC is not fully qualified.')
                                    'The logger path is borg.debug.<TOPIC> if TOPIC is not fully qualified.')
             add_common_option('-p', '--progress', dest='progress', action='store_true',
             add_common_option('-p', '--progress', dest='progress', action='store_true',
                               help='show progress information')
                               help='show progress information')
             add_common_option('--log-json', dest='log_json', action='store_true',
             add_common_option('--log-json', dest='log_json', action='store_true',
                               help='Output one JSON object per log line instead of formatted text.')
                               help='Output one JSON object per log line instead of formatted text.')
-            add_common_option('--lock-wait', dest='lock_wait', type=int, metavar='N', default=1,
-                              help='wait for the lock, but max. N seconds (default: %(default)d).')
+            add_common_option('--lock-wait', metavar='SECONDS', dest='lock_wait', type=int, default=1,
+                              help='wait at most SECONDS for acquiring a repository/cache lock (default: %(default)d).')
             add_common_option('--show-version', dest='show_version', action='store_true',
             add_common_option('--show-version', dest='show_version', action='store_true',
                               help='show/log the borg version')
                               help='show/log the borg version')
             add_common_option('--show-rc', dest='show_rc', action='store_true',
             add_common_option('--show-rc', dest='show_rc', action='store_true',
                               help='show/log the return code (rc)')
                               help='show/log the return code (rc)')
             add_common_option('--no-files-cache', dest='cache_files', action='store_false',
             add_common_option('--no-files-cache', dest='cache_files', action='store_false',
                               help='do not load/update the file metadata cache used to detect unchanged files')
                               help='do not load/update the file metadata cache used to detect unchanged files')
-            add_common_option('--umask', dest='umask', type=lambda s: int(s, 8), default=UMASK_DEFAULT, metavar='M',
+            add_common_option('--umask', metavar='M', dest='umask', type=lambda s: int(s, 8), default=UMASK_DEFAULT,
                               help='set umask to M (local and remote, default: %(default)04o)')
                               help='set umask to M (local and remote, default: %(default)04o)')
-            add_common_option('--remote-path', dest='remote_path', metavar='PATH',
+            add_common_option('--remote-path', metavar='PATH', dest='remote_path',
                               help='use PATH as borg executable on the remote (default: "borg")')
                               help='use PATH as borg executable on the remote (default: "borg")')
-            add_common_option('--remote-ratelimit', dest='remote_ratelimit', type=int, metavar='rate',
+            add_common_option('--remote-ratelimit', metavar='RATE', dest='remote_ratelimit', type=int,
                               help='set remote network upload rate limit in kiByte/s (default: 0=unlimited)')
                               help='set remote network upload rate limit in kiByte/s (default: 0=unlimited)')
-            add_common_option('--consider-part-files', dest='consider_part_files',
-                              action='store_true',
+            add_common_option('--consider-part-files', dest='consider_part_files', action='store_true',
                               help='treat part files like normal files (e.g. to list/extract them)')
                               help='treat part files like normal files (e.g. to list/extract them)')
-            add_common_option('--debug-profile', dest='debug_profile', default=None, metavar='FILE',
+            add_common_option('--debug-profile', metavar='FILE', dest='debug_profile', default=None,
                               help='Write execution profile in Borg format into FILE. For local use a Python-'
                               help='Write execution profile in Borg format into FILE. For local use a Python-'
                                    'compatible file can be generated by suffixing FILE with ".pyprof".')
                                    'compatible file can be generated by suffixing FILE with ".pyprof".')
 
 
+        def define_exclude_and_patterns(add_option, *, tag_files=False, strip_components=False):
+            add_option('-e', '--exclude', metavar='PATTERN', dest='patterns',
+                       type=parse_exclude_pattern, action='append',
+                       help='exclude paths matching PATTERN')
+            add_option('--exclude-from', metavar='EXCLUDEFILE', action=ArgparseExcludeFileAction,
+                       help='read exclude patterns from EXCLUDEFILE, one per line')
+            add_option('--pattern', metavar='PATTERN', action=ArgparsePatternAction,
+                       help='experimental: include/exclude paths matching PATTERN')
+            add_option('--patterns-from', metavar='PATTERNFILE', action=ArgparsePatternFileAction,
+                       help='experimental: read include/exclude patterns from PATTERNFILE, one per line')
+
+            if tag_files:
+                add_option('--exclude-caches', dest='exclude_caches', action='store_true',
+                           help='exclude directories that contain a CACHEDIR.TAG file '
+                                '(http://www.brynosaurus.com/cachedir/spec.html)')
+                add_option('--exclude-if-present', metavar='NAME', dest='exclude_if_present',
+                           action='append', type=str,
+                           help='exclude directories that are tagged by containing a filesystem object with '
+                                'the given NAME')
+                add_option('--keep-exclude-tags', '--keep-tag-files', dest='keep_exclude_tags',
+                           action='store_true',
+                           help='if tag objects are specified with ``--exclude-if-present``, '
+                                'don\'t omit the tag objects themselves from the backup archive')
+
+            if strip_components:
+                add_option('--strip-components', metavar='NUMBER', dest='strip_components', type=int, default=0,
+                           help='Remove the specified number of leading path elements. '
+                                'Paths with fewer elements will be silently skipped.')
+
+        def define_exclusion_group(subparser, **kwargs):
+            exclude_group = subparser.add_argument_group('Exclusion options')
+            define_exclude_and_patterns(exclude_group.add_argument, **kwargs)
+
+        def define_archive_filters_group(subparser, *, sort_by=True, first_last=True):
+            filters_group = subparser.add_argument_group('Archive filters',
+                                                         'Archive filters can be applied to repository targets.')
+            group = filters_group.add_mutually_exclusive_group()
+            group.add_argument('-P', '--prefix', metavar='PREFIX', dest='prefix', type=PrefixSpec, default='',
+                               help='only consider archive names starting with this prefix.')
+            group.add_argument('-a', '--glob-archives', metavar='GLOB', dest='glob_archives', default=None,
+                               help='only consider archive names matching the glob. '
+                                    'sh: rules apply, see "borg help patterns". '
+                                    '``--prefix`` and ``--glob-archives`` are mutually exclusive.')
+
+            if sort_by:
+                sort_by_default = 'timestamp'
+                filters_group.add_argument('--sort-by', metavar='KEYS', dest='sort_by',
+                                           type=SortBySpec, default=sort_by_default,
+                                           help='Comma-separated list of sorting keys; valid keys are: {}; default is: {}'
+                                           .format(', '.join(HUMAN_SORT_KEYS), sort_by_default))
+
+            if first_last:
+                group = filters_group.add_mutually_exclusive_group()
+                group.add_argument('--first', metavar='N', dest='first', default=0, type=int,
+                                   help='consider first N archives after other filters were applied')
+                group.add_argument('--last', metavar='N', dest='last', default=0, type=int,
+                                   help='consider last N archives after other filters were applied')
+
         parser = argparse.ArgumentParser(prog=self.prog, description='Borg - Deduplicated Backups',
         parser = argparse.ArgumentParser(prog=self.prog, description='Borg - Deduplicated Backups',
                                          add_help=False)
                                          add_help=False)
         parser.common_options = self.CommonOptions(define_common_options,
         parser.common_options = self.CommonOptions(define_common_options,
@@ -2360,22 +2416,22 @@ class Archiver:
                                           formatter_class=argparse.RawDescriptionHelpFormatter,
                                           formatter_class=argparse.RawDescriptionHelpFormatter,
                                           help='start repository server process')
                                           help='start repository server process')
         subparser.set_defaults(func=self.do_serve)
         subparser.set_defaults(func=self.do_serve)
-        subparser.add_argument('--restrict-to-path', dest='restrict_to_paths', action='append',
-                               metavar='PATH', help='restrict repository access to PATH. '
-                                                    'Can be specified multiple times to allow the client access to several directories. '
-                                                    'Access to all sub-directories is granted implicitly; PATH doesn\'t need to directly point to a repository.')
-        subparser.add_argument('--restrict-to-repository', dest='restrict_to_repositories', action='append',
-                               metavar='PATH', help='restrict repository access. Only the repository located at PATH (no sub-directories are considered) '
-                                                    'is accessible. '
-                                                    'Can be specified multiple times to allow the client access to several repositories. '
-                                                    'Unlike ``--restrict-to-path`` sub-directories are not accessible; '
-                                                    'PATH needs to directly point at a repository location. '
-                                                    'PATH may be an empty directory or the last element of PATH may not exist, in which case '
-                                                    'the client may initialize a repository there.')
+        subparser.add_argument('--restrict-to-path', metavar='PATH', dest='restrict_to_paths', action='append',
+                               help='restrict repository access to PATH. '
+                                    'Can be specified multiple times to allow the client access to several directories. '
+                                    'Access to all sub-directories is granted implicitly; PATH doesn\'t need to directly point to a repository.')
+        subparser.add_argument('--restrict-to-repository', metavar='PATH', dest='restrict_to_repositories', action='append',
+                                help='restrict repository access. Only the repository located at PATH '
+                                     '(no sub-directories are considered) is accessible. '
+                                     'Can be specified multiple times to allow the client access to several repositories. '
+                                     'Unlike ``--restrict-to-path`` sub-directories are not accessible; '
+                                     'PATH needs to directly point at a repository location. '
+                                     'PATH may be an empty directory or the last element of PATH may not exist, in which case '
+                                     'the client may initialize a repository there.')
         subparser.add_argument('--append-only', dest='append_only', action='store_true',
         subparser.add_argument('--append-only', dest='append_only', action='store_true',
                                help='only allow appending to repository segment files')
                                help='only allow appending to repository segment files')
-        subparser.add_argument('--storage-quota', dest='storage_quota', default=None,
-                               type=parse_storage_quota,
+        subparser.add_argument('--storage-quota', metavar='QUOTA', dest='storage_quota',
+                               type=parse_storage_quota, default=None,
                                help='Override storage quota of the repository (e.g. 5G, 1.5T). '
                                help='Override storage quota of the repository (e.g. 5G, 1.5T). '
                                     'When a new repository is initialized, sets the storage quota on the new '
                                     'When a new repository is initialized, sets the storage quota on the new '
                                     'repository as well. Default: no quota.')
                                     'repository as well. Default: no quota.')
@@ -2491,12 +2547,12 @@ class Archiver:
         subparser.add_argument('location', metavar='REPOSITORY', nargs='?', default='',
         subparser.add_argument('location', metavar='REPOSITORY', nargs='?', default='',
                                type=location_validator(archive=False),
                                type=location_validator(archive=False),
                                help='repository to create')
                                help='repository to create')
-        subparser.add_argument('-e', '--encryption', dest='encryption', required=True,
+        subparser.add_argument('-e', '--encryption', metavar='MODE', dest='encryption', required=True,
                                choices=('none', 'keyfile', 'repokey', 'keyfile-blake2', 'repokey-blake2', 'authenticated'),
                                choices=('none', 'keyfile', 'repokey', 'keyfile-blake2', 'repokey-blake2', 'authenticated'),
                                help='select encryption key mode **(required)**')
                                help='select encryption key mode **(required)**')
         subparser.add_argument('--append-only', dest='append_only', action='store_true',
         subparser.add_argument('--append-only', dest='append_only', action='store_true',
                                help='create an append-only mode repository')
                                help='create an append-only mode repository')
-        subparser.add_argument('--storage-quota', dest='storage_quota', default=None,
+        subparser.add_argument('--storage-quota', metavar='QUOTA', dest='storage_quota', default=None,
                                type=parse_storage_quota,
                                type=parse_storage_quota,
                                help='Set storage quota of the new repository (e.g. 5G, 1.5T). Default: no quota.')
                                help='Set storage quota of the new repository (e.g. 5G, 1.5T). Default: no quota.')
 
 
@@ -2557,22 +2613,17 @@ class Archiver:
                                type=location_validator(),
                                type=location_validator(),
                                help='repository or archive to check consistency of')
                                help='repository or archive to check consistency of')
         subparser.add_argument('--repository-only', dest='repo_only', action='store_true',
         subparser.add_argument('--repository-only', dest='repo_only', action='store_true',
-                               default=False,
                                help='only perform repository checks')
                                help='only perform repository checks')
         subparser.add_argument('--archives-only', dest='archives_only', action='store_true',
         subparser.add_argument('--archives-only', dest='archives_only', action='store_true',
-                               default=False,
                                help='only perform archives checks')
                                help='only perform archives checks')
         subparser.add_argument('--verify-data', dest='verify_data', action='store_true',
         subparser.add_argument('--verify-data', dest='verify_data', action='store_true',
-                               default=False,
                                help='perform cryptographic archive data integrity verification '
                                help='perform cryptographic archive data integrity verification '
                                     '(conflicts with ``--repository-only``)')
                                     '(conflicts with ``--repository-only``)')
         subparser.add_argument('--repair', dest='repair', action='store_true',
         subparser.add_argument('--repair', dest='repair', action='store_true',
-                               default=False,
                                help='attempt to repair any inconsistencies found')
                                help='attempt to repair any inconsistencies found')
         subparser.add_argument('--save-space', dest='save_space', action='store_true',
         subparser.add_argument('--save-space', dest='save_space', action='store_true',
-                               default=False,
                                help='work slower, but using less space')
                                help='work slower, but using less space')
-        self.add_archives_filters_args(subparser)
+        define_archive_filters_group(subparser)
 
 
         subparser = subparsers.add_parser('key', parents=[mid_common_parser], add_help=False,
         subparser = subparsers.add_parser('key', parents=[mid_common_parser], add_help=False,
                                           description="Manage a keyfile or repokey of a repository",
                                           description="Manage a keyfile or repokey of a repository",
@@ -2613,10 +2664,8 @@ class Archiver:
         subparser.add_argument('path', metavar='PATH', nargs='?', type=str,
         subparser.add_argument('path', metavar='PATH', nargs='?', type=str,
                                help='where to store the backup')
                                help='where to store the backup')
         subparser.add_argument('--paper', dest='paper', action='store_true',
         subparser.add_argument('--paper', dest='paper', action='store_true',
-                               default=False,
                                help='Create an export suitable for printing and later type-in')
                                help='Create an export suitable for printing and later type-in')
         subparser.add_argument('--qr-html', dest='qr', action='store_true',
         subparser.add_argument('--qr-html', dest='qr', action='store_true',
-                               default=False,
                                help='Create an html file suitable for printing and later type-in or qr scan')
                                help='Create an html file suitable for printing and later type-in or qr scan')
 
 
         key_import_epilog = process_epilog("""
         key_import_epilog = process_epilog("""
@@ -2638,7 +2687,6 @@ class Archiver:
         subparser.add_argument('path', metavar='PATH', nargs='?', type=str,
         subparser.add_argument('path', metavar='PATH', nargs='?', type=str,
                                help='path to the backup')
                                help='path to the backup')
         subparser.add_argument('--paper', dest='paper', action='store_true',
         subparser.add_argument('--paper', dest='paper', action='store_true',
-                               default=False,
                                help='interactively import from a backup done with ``--paper``')
                                help='interactively import from a backup done with ``--paper``')
 
 
         change_passphrase_epilog = process_epilog("""
         change_passphrase_epilog = process_epilog("""
@@ -2790,86 +2838,52 @@ class Archiver:
                                           help='create backup')
                                           help='create backup')
         subparser.set_defaults(func=self.do_create)
         subparser.set_defaults(func=self.do_create)
 
 
-        subparser.add_argument('-n', '--dry-run', dest='dry_run',
-                               action='store_true', default=False,
+        subparser.add_argument('-n', '--dry-run', dest='dry_run', action='store_true',
                                help='do not create a backup archive')
                                help='do not create a backup archive')
-
-        subparser.add_argument('-s', '--stats', dest='stats',
-                               action='store_true', default=False,
+        subparser.add_argument('-s', '--stats', dest='stats', action='store_true',
                                help='print statistics for the created archive')
                                help='print statistics for the created archive')
-        subparser.add_argument('--list', dest='output_list',
-                               action='store_true', default=False,
+        subparser.add_argument('--list', dest='output_list', action='store_true',
                                help='output verbose list of items (files, dirs, ...)')
                                help='output verbose list of items (files, dirs, ...)')
-        subparser.add_argument('--filter', dest='output_filter', metavar='STATUSCHARS',
-                               help='only display items with the given status characters')
+        subparser.add_argument('--filter', metavar='STATUSCHARS', dest='output_filter',
+                               help='only display items with the given status characters (see description)')
         subparser.add_argument('--json', action='store_true',
         subparser.add_argument('--json', action='store_true',
                                help='output stats as JSON. Implies ``--stats``.')
                                help='output stats as JSON. Implies ``--stats``.')
         subparser.add_argument('--no-cache-sync', dest='no_cache_sync', action='store_true',
         subparser.add_argument('--no-cache-sync', dest='no_cache_sync', action='store_true',
                                help='experimental: do not synchronize the cache. Implies ``--no-files-cache``.')
                                help='experimental: do not synchronize the cache. Implies ``--no-files-cache``.')
 
 
-        exclude_group = subparser.add_argument_group('Exclusion options')
-        exclude_group.add_argument('-e', '--exclude', dest='patterns',
-                                   type=parse_exclude_pattern, action='append',
-                                   metavar="PATTERN", help='exclude paths matching PATTERN')
-        exclude_group.add_argument('--exclude-from', action=ArgparseExcludeFileAction,
-                                   metavar='EXCLUDEFILE', help='read exclude patterns from EXCLUDEFILE, one per line')
-        exclude_group.add_argument('--exclude-caches', dest='exclude_caches',
-                                   action='store_true', default=False,
-                                   help='exclude directories that contain a CACHEDIR.TAG file ('
-                                        'http://www.brynosaurus.com/cachedir/spec.html)')
-        exclude_group.add_argument('--exclude-if-present', dest='exclude_if_present',
-                                   metavar='NAME', action='append', type=str,
-                                   help='exclude directories that are tagged by containing a filesystem object with '
-                                        'the given NAME')
-        exclude_group.add_argument('--keep-exclude-tags', '--keep-tag-files', dest='keep_exclude_tags',
-                                   action='store_true', default=False,
-                                   help='if tag objects are specified with ``--exclude-if-present``, '
-                                        'don\'t omit the tag objects themselves from the backup archive')
-        exclude_group.add_argument('--pattern',
-                                   action=ArgparsePatternAction,
-                                   metavar="PATTERN", help='experimental: include/exclude paths matching PATTERN')
-        exclude_group.add_argument('--patterns-from', action=ArgparsePatternFileAction,
-                                   metavar='PATTERNFILE', help='experimental: read include/exclude patterns from PATTERNFILE, one per line')
+        define_exclusion_group(subparser, tag_files=True)
 
 
         fs_group = subparser.add_argument_group('Filesystem options')
         fs_group = subparser.add_argument_group('Filesystem options')
-        fs_group.add_argument('-x', '--one-file-system', dest='one_file_system',
-                              action='store_true', default=False,
+        fs_group.add_argument('-x', '--one-file-system', dest='one_file_system', action='store_true',
                               help='stay in the same file system and do not store mount points of other file systems')
                               help='stay in the same file system and do not store mount points of other file systems')
-        fs_group.add_argument('--numeric-owner', dest='numeric_owner',
-                              action='store_true', default=False,
+        fs_group.add_argument('--numeric-owner', dest='numeric_owner', action='store_true',
                               help='only store numeric user and group identifiers')
                               help='only store numeric user and group identifiers')
-        fs_group.add_argument('--noatime', dest='noatime',
-                              action='store_true', default=False,
+        fs_group.add_argument('--noatime', dest='noatime', action='store_true',
                               help='do not store atime into archive')
                               help='do not store atime into archive')
-        fs_group.add_argument('--noctime', dest='noctime',
-                              action='store_true', default=False,
+        fs_group.add_argument('--noctime', dest='noctime', action='store_true',
                               help='do not store ctime into archive')
                               help='do not store ctime into archive')
-        fs_group.add_argument('--ignore-inode', dest='ignore_inode',
-                              action='store_true', default=False,
+        fs_group.add_argument('--ignore-inode', dest='ignore_inode', action='store_true',
                               help='ignore inode data in the file metadata cache used to detect unchanged files.')
                               help='ignore inode data in the file metadata cache used to detect unchanged files.')
-        fs_group.add_argument('--read-special', dest='read_special',
-                              action='store_true', default=False,
+        fs_group.add_argument('--read-special', dest='read_special', action='store_true',
                               help='open and read block and char device files as well as FIFOs as if they were '
                               help='open and read block and char device files as well as FIFOs as if they were '
                                    'regular files. Also follows symlinks pointing to these kinds of files.')
                                    'regular files. Also follows symlinks pointing to these kinds of files.')
 
 
         archive_group = subparser.add_argument_group('Archive options')
         archive_group = subparser.add_argument_group('Archive options')
         archive_group.add_argument('--comment', dest='comment', metavar='COMMENT', default='',
         archive_group.add_argument('--comment', dest='comment', metavar='COMMENT', default='',
                                    help='add a comment text to the archive')
                                    help='add a comment text to the archive')
-        archive_group.add_argument('--timestamp', dest='timestamp',
+        archive_group.add_argument('--timestamp', metavar='TIMESTAMP', dest='timestamp',
                                    type=timestamp, default=None,
                                    type=timestamp, default=None,
-                                   metavar='TIMESTAMP',
                                    help='manually specify the archive creation date/time (UTC, yyyy-mm-ddThh:mm:ss format). '
                                    help='manually specify the archive creation date/time (UTC, yyyy-mm-ddThh:mm:ss format). '
-                                        'alternatively, give a reference file/directory.')
-        archive_group.add_argument('-c', '--checkpoint-interval', dest='checkpoint_interval',
-                                   type=int, default=1800, metavar='SECONDS',
+                                        'Alternatively, give a reference file/directory.')
+        archive_group.add_argument('-c', '--checkpoint-interval', metavar='SECONDS', dest='checkpoint_interval',
+                                   type=int, default=1800,
                                    help='write checkpoint every SECONDS seconds (Default: 1800)')
                                    help='write checkpoint every SECONDS seconds (Default: 1800)')
-        archive_group.add_argument('--chunker-params', dest='chunker_params',
+        archive_group.add_argument('--chunker-params', metavar='PARAMS', dest='chunker_params',
                                    type=ChunkerParams, default=CHUNKER_PARAMS,
                                    type=ChunkerParams, default=CHUNKER_PARAMS,
-                                   metavar='PARAMS',
                                    help='specify the chunker parameters (CHUNK_MIN_EXP, CHUNK_MAX_EXP, '
                                    help='specify the chunker parameters (CHUNK_MIN_EXP, CHUNK_MAX_EXP, '
                                         'HASH_MASK_BITS, HASH_WINDOW_SIZE). default: %d,%d,%d,%d' % CHUNKER_PARAMS)
                                         'HASH_MASK_BITS, HASH_WINDOW_SIZE). default: %d,%d,%d,%d' % CHUNKER_PARAMS)
-        archive_group.add_argument('-C', '--compression', dest='compression',
-                                   type=CompressionSpec, default=CompressionSpec('lz4'), metavar='COMPRESSION',
+        archive_group.add_argument('-C', '--compression', metavar='COMPRESSION', dest='compression',
+                                   type=CompressionSpec, default=CompressionSpec('lz4'),
                                    help='select compression algorithm, see the output of the '
                                    help='select compression algorithm, see the output of the '
                                         '"borg help compression" command for details.')
                                         '"borg help compression" command for details.')
 
 
@@ -2905,38 +2919,22 @@ class Archiver:
                                           formatter_class=argparse.RawDescriptionHelpFormatter,
                                           formatter_class=argparse.RawDescriptionHelpFormatter,
                                           help='extract archive contents')
                                           help='extract archive contents')
         subparser.set_defaults(func=self.do_extract)
         subparser.set_defaults(func=self.do_extract)
-        subparser.add_argument('--list', dest='output_list',
-                               action='store_true', default=False,
+        subparser.add_argument('--list', dest='output_list', action='store_true',
                                help='output verbose list of items (files, dirs, ...)')
                                help='output verbose list of items (files, dirs, ...)')
-        subparser.add_argument('-n', '--dry-run', dest='dry_run',
-                               default=False, action='store_true',
+        subparser.add_argument('-n', '--dry-run', dest='dry_run', action='store_true',
                                help='do not actually change any files')
                                help='do not actually change any files')
-        subparser.add_argument('-e', '--exclude', dest='patterns',
-                               type=parse_exclude_pattern, action='append',
-                               metavar="PATTERN", help='exclude paths matching PATTERN')
-        subparser.add_argument('--exclude-from', action=ArgparseExcludeFileAction,
-                               metavar='EXCLUDEFILE', help='read exclude patterns from EXCLUDEFILE, one per line')
-        subparser.add_argument('--pattern', action=ArgparsePatternAction,
-                               metavar="PATTERN", help='experimental: include/exclude paths matching PATTERN')
-        subparser.add_argument('--patterns-from', action=ArgparsePatternFileAction,
-                               metavar='PATTERNFILE', help='experimental: read include/exclude patterns from PATTERNFILE, one per line')
-        subparser.add_argument('--numeric-owner', dest='numeric_owner',
-                               action='store_true', default=False,
+        subparser.add_argument('--numeric-owner', dest='numeric_owner', action='store_true',
                                help='only obey numeric user and group identifiers')
                                help='only obey numeric user and group identifiers')
-        subparser.add_argument('--strip-components', dest='strip_components',
-                               type=int, default=0, metavar='NUMBER',
-                               help='Remove the specified number of leading path elements. Pathnames with fewer elements will be silently skipped.')
-        subparser.add_argument('--stdout', dest='stdout',
-                               action='store_true', default=False,
+        subparser.add_argument('--stdout', dest='stdout', action='store_true',
                                help='write all extracted data to stdout')
                                help='write all extracted data to stdout')
-        subparser.add_argument('--sparse', dest='sparse',
-                               action='store_true', default=False,
+        subparser.add_argument('--sparse', dest='sparse', action='store_true',
                                help='create holes in output sparse file from all-zero chunks')
                                help='create holes in output sparse file from all-zero chunks')
         subparser.add_argument('location', metavar='ARCHIVE',
         subparser.add_argument('location', metavar='ARCHIVE',
                                type=location_validator(archive=True),
                                type=location_validator(archive=True),
                                help='archive to extract')
                                help='archive to extract')
         subparser.add_argument('paths', metavar='PATH', nargs='*', type=str,
         subparser.add_argument('paths', metavar='PATH', nargs='*', type=str,
                                help='paths to extract; patterns are supported')
                                help='paths to extract; patterns are supported')
+        define_exclusion_group(subparser, strip_components=True)
 
 
         export_tar_epilog = process_epilog("""
         export_tar_epilog = process_epilog("""
         This command creates a tarball from an archive.
         This command creates a tarball from an archive.
@@ -2981,21 +2979,8 @@ class Archiver:
         subparser.set_defaults(func=self.do_export_tar)
         subparser.set_defaults(func=self.do_export_tar)
         subparser.add_argument('--tar-filter', dest='tar_filter', default='auto',
         subparser.add_argument('--tar-filter', dest='tar_filter', default='auto',
                                help='filter program to pipe data through')
                                help='filter program to pipe data through')
-        subparser.add_argument('--list', dest='output_list',
-                               action='store_true', default=False,
+        subparser.add_argument('--list', dest='output_list', action='store_true',
                                help='output verbose list of items (files, dirs, ...)')
                                help='output verbose list of items (files, dirs, ...)')
-        subparser.add_argument('-e', '--exclude', dest='patterns',
-                               type=parse_exclude_pattern, action='append',
-                               metavar="PATTERN", help='exclude paths matching PATTERN')
-        subparser.add_argument('--exclude-from', action=ArgparseExcludeFileAction,
-                               metavar='EXCLUDEFILE', help='read exclude patterns from EXCLUDEFILE, one per line')
-        subparser.add_argument('--pattern', action=ArgparsePatternAction,
-                               metavar="PATTERN", help='experimental: include/exclude paths matching PATTERN')
-        subparser.add_argument('--patterns-from', action=ArgparsePatternFileAction,
-                               metavar='PATTERNFILE', help='experimental: read include/exclude patterns from PATTERNFILE, one per line')
-        subparser.add_argument('--strip-components', dest='strip_components',
-                               type=int, default=0, metavar='NUMBER',
-                               help='Remove the specified number of leading path elements. Pathnames with fewer elements will be silently skipped.')
         subparser.add_argument('location', metavar='ARCHIVE',
         subparser.add_argument('location', metavar='ARCHIVE',
                                type=location_validator(archive=True),
                                type=location_validator(archive=True),
                                help='archive to export')
                                help='archive to export')
@@ -3003,6 +2988,7 @@ class Archiver:
                                help='output tar file. "-" to write to stdout instead.')
                                help='output tar file. "-" to write to stdout instead.')
         subparser.add_argument('paths', metavar='PATH', nargs='*', type=str,
         subparser.add_argument('paths', metavar='PATH', nargs='*', type=str,
                                help='paths to extract; patterns are supported')
                                help='paths to extract; patterns are supported')
+        define_exclusion_group(subparser, strip_components=True)
 
 
         diff_epilog = process_epilog("""
         diff_epilog = process_epilog("""
             This command finds differences (file contents, user/group/mode) between archives.
             This command finds differences (file contents, user/group/mode) between archives.
@@ -3028,14 +3014,11 @@ class Archiver:
                                           formatter_class=argparse.RawDescriptionHelpFormatter,
                                           formatter_class=argparse.RawDescriptionHelpFormatter,
                                           help='find differences in archive contents')
                                           help='find differences in archive contents')
         subparser.set_defaults(func=self.do_diff)
         subparser.set_defaults(func=self.do_diff)
-        subparser.add_argument('--numeric-owner', dest='numeric_owner',
-                               action='store_true', default=False,
+        subparser.add_argument('--numeric-owner', dest='numeric_owner', action='store_true',
                                help='only consider numeric user and group identifiers')
                                help='only consider numeric user and group identifiers')
-        subparser.add_argument('--same-chunker-params', dest='same_chunker_params',
-                               action='store_true', default=False,
+        subparser.add_argument('--same-chunker-params', dest='same_chunker_params', action='store_true',
                                help='Override check of chunker parameters.')
                                help='Override check of chunker parameters.')
-        subparser.add_argument('--sort', dest='sort',
-                               action='store_true', default=False,
+        subparser.add_argument('--sort', dest='sort', action='store_true',
                                help='Sort the output lines by file path.')
                                help='Sort the output lines by file path.')
         subparser.add_argument('location', metavar='REPO_ARCHIVE1',
         subparser.add_argument('location', metavar='REPO_ARCHIVE1',
                                type=location_validator(archive=True),
                                type=location_validator(archive=True),
@@ -3045,30 +3028,7 @@ class Archiver:
                                help='ARCHIVE2 name (no repository location allowed)')
                                help='ARCHIVE2 name (no repository location allowed)')
         subparser.add_argument('paths', metavar='PATH', nargs='*', type=str,
         subparser.add_argument('paths', metavar='PATH', nargs='*', type=str,
                                help='paths of items inside the archives to compare; patterns are supported')
                                help='paths of items inside the archives to compare; patterns are supported')
-
-        exclude_group = subparser.add_argument_group('Exclusion options')
-        exclude_group.add_argument('-e', '--exclude', dest='patterns',
-                                   type=parse_exclude_pattern, action='append',
-                                   metavar="PATTERN", help='exclude paths matching PATTERN')
-        exclude_group.add_argument('--exclude-from', action=ArgparseExcludeFileAction,
-                                   metavar='EXCLUDEFILE', help='read exclude patterns from EXCLUDEFILE, one per line')
-        exclude_group.add_argument('--exclude-caches', dest='exclude_caches',
-                                   action='store_true', default=False,
-                                   help='exclude directories that contain a CACHEDIR.TAG file ('
-                                        'http://www.brynosaurus.com/cachedir/spec.html)')
-        exclude_group.add_argument('--exclude-if-present', dest='exclude_if_present',
-                                   metavar='NAME', action='append', type=str,
-                                   help='exclude directories that are tagged by containing a filesystem object with '
-                                        'the given NAME')
-        exclude_group.add_argument('--keep-exclude-tags', '--keep-tag-files', dest='keep_exclude_tags',
-                                   action='store_true', default=False,
-                                   help='if tag objects are specified with ``--exclude-if-present``, '
-                                        'don\'t omit the tag objects themselves from the backup archive')
-        exclude_group.add_argument('--pattern',
-                                   action=ArgparsePatternAction,
-                                   metavar="PATTERN", help='experimental: include/exclude paths matching PATTERN')
-        exclude_group.add_argument('--patterns-from', action=ArgparsePatternFileAction,
-                                   metavar='PATTERNFILE', help='experimental: read include/exclude patterns from PATTERNFILE, one per line')
+        define_exclusion_group(subparser, tag_files=True)
 
 
         rename_epilog = process_epilog("""
         rename_epilog = process_epilog("""
         This command renames an archive in the repository.
         This command renames an archive in the repository.
@@ -3108,12 +3068,11 @@ class Archiver:
                                help='force deletion of corrupted archives, '
                                help='force deletion of corrupted archives, '
                                     'use ``--force --force`` in case ``--force`` does not work.')
                                     'use ``--force --force`` in case ``--force`` does not work.')
         subparser.add_argument('--save-space', dest='save_space', action='store_true',
         subparser.add_argument('--save-space', dest='save_space', action='store_true',
-                               default=False,
                                help='work slower, but using less space')
                                help='work slower, but using less space')
         subparser.add_argument('location', metavar='TARGET', nargs='?', default='',
         subparser.add_argument('location', metavar='TARGET', nargs='?', default='',
                                type=location_validator(),
                                type=location_validator(),
                                help='archive or repository to delete')
                                help='archive or repository to delete')
-        self.add_archives_filters_args(subparser)
+        define_archive_filters_group(subparser)
 
 
         list_epilog = process_epilog("""
         list_epilog = process_epilog("""
         This command lists the contents of a repository or an archive.
         This command lists the contents of a repository or an archive.
@@ -3142,7 +3101,7 @@ class Archiver:
         subparser.set_defaults(func=self.do_list)
         subparser.set_defaults(func=self.do_list)
         subparser.add_argument('--short', dest='short', action='store_true',
         subparser.add_argument('--short', dest='short', action='store_true',
                                help='only print file/directory names, nothing else')
                                help='only print file/directory names, nothing else')
-        subparser.add_argument('--format', '--list-format', dest='format', type=str, metavar='FORMAT',
+        subparser.add_argument('--format', '--list-format', metavar='FORMAT', dest='format',
                                help='specify format for file listing '
                                help='specify format for file listing '
                                     '(default: "{mode} {user:6} {group:6} {size:8d} {isomtime} {path}{extra}{NL}")')
                                     '(default: "{mode} {user:6} {group:6} {size:8d} {isomtime} {path}{extra}{NL}")')
         subparser.add_argument('--json', action='store_true',
         subparser.add_argument('--json', action='store_true',
@@ -3162,30 +3121,8 @@ class Archiver:
                                help='repository/archive to list contents of')
                                help='repository/archive to list contents of')
         subparser.add_argument('paths', metavar='PATH', nargs='*', type=str,
         subparser.add_argument('paths', metavar='PATH', nargs='*', type=str,
                                help='paths to list; patterns are supported')
                                help='paths to list; patterns are supported')
-        self.add_archives_filters_args(subparser)
-
-        exclude_group = subparser.add_argument_group('Exclusion options')
-        exclude_group.add_argument('-e', '--exclude', dest='patterns',
-                                   type=parse_exclude_pattern, action='append',
-                                   metavar="PATTERN", help='exclude paths matching PATTERN')
-        exclude_group.add_argument('--exclude-from', action=ArgparseExcludeFileAction,
-                                   metavar='EXCLUDEFILE', help='read exclude patterns from EXCLUDEFILE, one per line')
-        exclude_group.add_argument('--exclude-caches', dest='exclude_caches', action='store_true',
-                                   help='exclude directories that contain a CACHEDIR.TAG file ('
-                                        'http://www.brynosaurus.com/cachedir/spec.html)')
-        exclude_group.add_argument('--exclude-if-present', dest='exclude_if_present',
-                                   metavar='NAME', action='append', type=str,
-                                   help='exclude directories that are tagged by containing a filesystem object with '
-                                        'the given NAME')
-        exclude_group.add_argument('--keep-exclude-tags', '--keep-tag-files', dest='keep_exclude_tags',
-                                   action='store_true',
-                                   help='if tag objects are specified with ``--exclude-if-present``, don\'t omit the tag '
-                                        'objects themselves from the backup archive')
-        exclude_group.add_argument('--pattern',
-                                   action=ArgparsePatternAction,
-                                   metavar="PATTERN", help='experimental: include/exclude paths matching PATTERN')
-        exclude_group.add_argument('--patterns-from', action=ArgparsePatternFileAction,
-                                   metavar='PATTERNFILE', help='experimental: read include/exclude patterns from PATTERNFILE, one per line')
+        define_archive_filters_group(subparser)
+        define_exclusion_group(subparser, tag_files=True)
 
 
         mount_epilog = process_epilog("""
         mount_epilog = process_epilog("""
         This command mounts an archive as a FUSE filesystem. This can be useful for
         This command mounts an archive as a FUSE filesystem. This can be useful for
@@ -3236,7 +3173,7 @@ class Archiver:
                                help='stay in foreground, do not daemonize')
                                help='stay in foreground, do not daemonize')
         subparser.add_argument('-o', dest='options', type=str,
         subparser.add_argument('-o', dest='options', type=str,
                                help='Extra mount options')
                                help='Extra mount options')
-        self.add_archives_filters_args(subparser)
+        define_archive_filters_group(subparser)
 
 
         umount_epilog = process_epilog("""
         umount_epilog = process_epilog("""
         This command un-mounts a FUSE filesystem that was mounted with ``borg mount``.
         This command un-mounts a FUSE filesystem that was mounted with ``borg mount``.
@@ -3276,7 +3213,7 @@ class Archiver:
                                help='archive or repository to display information about')
                                help='archive or repository to display information about')
         subparser.add_argument('--json', action='store_true',
         subparser.add_argument('--json', action='store_true',
                                help='format output as JSON')
                                help='format output as JSON')
-        self.add_archives_filters_args(subparser)
+        define_archive_filters_group(subparser)
 
 
         break_lock_epilog = process_epilog("""
         break_lock_epilog = process_epilog("""
         This command breaks the repository and cache locks.
         This command breaks the repository and cache locks.
@@ -3347,7 +3284,7 @@ class Archiver:
                                help='print statistics for the deleted archive')
                                help='print statistics for the deleted archive')
         subparser.add_argument('--list', dest='output_list', action='store_true',
         subparser.add_argument('--list', dest='output_list', action='store_true',
                                help='output verbose list of archives it keeps/prunes')
                                help='output verbose list of archives it keeps/prunes')
-        subparser.add_argument('--keep-within', dest='within', type=interval, metavar='INTERVAL',
+        subparser.add_argument('--keep-within', metavar='INTERVAL', dest='within', type=interval,
                                help='keep all archives within this time interval')
                                help='keep all archives within this time interval')
         subparser.add_argument('--keep-last', '--keep-secondly', dest='secondly', type=int, default=0,
         subparser.add_argument('--keep-last', '--keep-secondly', dest='secondly', type=int, default=0,
                                help='number of secondly archives to keep')
                                help='number of secondly archives to keep')
@@ -3363,7 +3300,7 @@ class Archiver:
                                help='number of monthly archives to keep')
                                help='number of monthly archives to keep')
         subparser.add_argument('-y', '--keep-yearly', dest='yearly', type=int, default=0,
         subparser.add_argument('-y', '--keep-yearly', dest='yearly', type=int, default=0,
                                help='number of yearly archives to keep')
                                help='number of yearly archives to keep')
-        self.add_archives_filters_args(subparser, sort_by=False, first_last=False)
+        define_archive_filters_group(subparser, sort_by=False, first_last=False)
         subparser.add_argument('--save-space', dest='save_space', action='store_true',
         subparser.add_argument('--save-space', dest='save_space', action='store_true',
                                help='work slower, but using less space')
                                help='work slower, but using less space')
         subparser.add_argument('location', metavar='REPOSITORY', nargs='?', default='',
         subparser.add_argument('location', metavar='REPOSITORY', nargs='?', default='',
@@ -3526,29 +3463,7 @@ class Archiver:
         subparser.add_argument('-s', '--stats', dest='stats', action='store_true',
         subparser.add_argument('-s', '--stats', dest='stats', action='store_true',
                                help='print statistics at end')
                                help='print statistics at end')
 
 
-        exclude_group = subparser.add_argument_group('Exclusion options')
-        exclude_group.add_argument('-e', '--exclude', dest='patterns',
-                                   type=parse_exclude_pattern, action='append',
-                                   metavar="PATTERN", help='exclude paths matching PATTERN')
-        exclude_group.add_argument('--exclude-from', action=ArgparseExcludeFileAction,
-                                   metavar='EXCLUDEFILE', help='read exclude patterns from EXCLUDEFILE, one per line')
-        exclude_group.add_argument('--exclude-caches', dest='exclude_caches',
-                                   action='store_true',
-                                   help='exclude directories that contain a CACHEDIR.TAG file ('
-                                        'http://www.brynosaurus.com/cachedir/spec.html)')
-        exclude_group.add_argument('--exclude-if-present', dest='exclude_if_present',
-                                   metavar='NAME', action='append', type=str,
-                                   help='exclude directories that are tagged by containing a filesystem object with '
-                                        'the given NAME')
-        exclude_group.add_argument('--keep-exclude-tags', '--keep-tag-files', dest='keep_exclude_tags',
-                                   action='store_true',
-                                   help='if tag objects are specified with ``--exclude-if-present``, don\'t omit the tag '
-                                        'objects themselves from the backup archive')
-        exclude_group.add_argument('--pattern',
-                                   action=ArgparsePatternAction,
-                                   metavar="PATTERN", help='experimental: include/exclude paths matching PATTERN')
-        exclude_group.add_argument('--patterns-from', action=ArgparsePatternFileAction,
-                                   metavar='PATTERNFILE', help='experimental: read include/exclude patterns from PATTERNFILE, one per line')
+        define_exclusion_group(subparser, tag_files=True)
 
 
         archive_group = subparser.add_argument_group('Archive options')
         archive_group = subparser.add_argument_group('Archive options')
         archive_group.add_argument('--target', dest='target', metavar='TARGET', default=None,
         archive_group.add_argument('--target', dest='target', metavar='TARGET', default=None,
@@ -3560,13 +3475,12 @@ class Archiver:
                                    help='write checkpoint every SECONDS seconds (Default: 1800)')
                                    help='write checkpoint every SECONDS seconds (Default: 1800)')
         archive_group.add_argument('--comment', dest='comment', metavar='COMMENT', default=None,
         archive_group.add_argument('--comment', dest='comment', metavar='COMMENT', default=None,
                                    help='add a comment text to the archive')
                                    help='add a comment text to the archive')
-        archive_group.add_argument('--timestamp', dest='timestamp',
+        archive_group.add_argument('--timestamp', metavar='TIMESTAMP', dest='timestamp',
                                    type=timestamp, default=None,
                                    type=timestamp, default=None,
-                                   metavar='TIMESTAMP',
                                    help='manually specify the archive creation date/time (UTC, yyyy-mm-ddThh:mm:ss format). '
                                    help='manually specify the archive creation date/time (UTC, yyyy-mm-ddThh:mm:ss format). '
                                         'alternatively, give a reference file/directory.')
                                         'alternatively, give a reference file/directory.')
-        archive_group.add_argument('-C', '--compression', dest='compression',
-                                   type=CompressionSpec, default=CompressionSpec('lz4'), metavar='COMPRESSION',
+        archive_group.add_argument('-C', '--compression', metavar='COMPRESSION', dest='compression',
+                                   type=CompressionSpec, default=CompressionSpec('lz4'),
                                    help='select compression algorithm, see the output of the '
                                    help='select compression algorithm, see the output of the '
                                         '"borg help compression" command for details.')
                                         '"borg help compression" command for details.')
         archive_group.add_argument('--recompress', dest='recompress', nargs='?', default='never', const='if-different',
         archive_group.add_argument('--recompress', dest='recompress', nargs='?', default='never', const='if-different',
@@ -3575,9 +3489,8 @@ class Archiver:
                                         'When `always`, chunks that are already compressed that way are not skipped, '
                                         'When `always`, chunks that are already compressed that way are not skipped, '
                                         'but compressed again. Only the algorithm is considered for `if-different`, '
                                         'but compressed again. Only the algorithm is considered for `if-different`, '
                                         'not the compression level (if any).')
                                         'not the compression level (if any).')
-        archive_group.add_argument('--chunker-params', dest='chunker_params',
+        archive_group.add_argument('--chunker-params', metavar='PARAMS', dest='chunker_params',
                                    type=ChunkerParams, default=CHUNKER_PARAMS,
                                    type=ChunkerParams, default=CHUNKER_PARAMS,
-                                   metavar='PARAMS',
                                    help='specify the chunker parameters (CHUNK_MIN_EXP, CHUNK_MAX_EXP, '
                                    help='specify the chunker parameters (CHUNK_MIN_EXP, CHUNK_MAX_EXP, '
                                         'HASH_MASK_BITS, HASH_WINDOW_SIZE) or `default` to use the current defaults. '
                                         'HASH_MASK_BITS, HASH_WINDOW_SIZE) or `default` to use the current defaults. '
                                         'default: %d,%d,%d,%d' % CHUNKER_PARAMS)
                                         'default: %d,%d,%d,%d' % CHUNKER_PARAMS)
@@ -3850,31 +3763,6 @@ class Archiver:
 
 
         return parser
         return parser
 
 
-    @staticmethod
-    def add_archives_filters_args(subparser, sort_by=True, first_last=True):
-        filters_group = subparser.add_argument_group('filters', 'Archive filters can be applied to repository targets.')
-        group = filters_group.add_mutually_exclusive_group()
-        group.add_argument('-P', '--prefix', dest='prefix', type=PrefixSpec, default='', metavar='PREFIX',
-                           help='only consider archive names starting with this prefix.')
-        group.add_argument('-a', '--glob-archives', dest='glob_archives', default=None, metavar='GLOB',
-                           help='only consider archive names matching the glob. '
-                                'sh: rules apply, see "borg help patterns". '
-                                '``--prefix`` and ``--glob-archives`` are mutually exclusive.')
-
-        if sort_by:
-            sort_by_default = 'timestamp'
-            filters_group.add_argument('--sort-by', dest='sort_by', type=SortBySpec, default=sort_by_default,
-                                       metavar='KEYS',
-                                       help='Comma-separated list of sorting keys; valid keys are: {}; default is: {}'
-                                       .format(', '.join(HUMAN_SORT_KEYS), sort_by_default))
-
-        if first_last:
-            group = filters_group.add_mutually_exclusive_group()
-            group.add_argument('--first', dest='first', metavar='N', default=0, type=int,
-                               help='consider first N archives after other filters were applied')
-            group.add_argument('--last', dest='last', metavar='N', default=0, type=int,
-                               help='consider last N archives after other filters were applied')
-
     def get_args(self, argv, cmd):
     def get_args(self, argv, cmd):
         """usually, just returns argv, except if we deal with a ssh forced command for borg serve."""
         """usually, just returns argv, except if we deal with a ssh forced command for borg serve."""
         result = self.parse_args(argv[1:])
         result = self.parse_args(argv[1:])