2
0

arguments.py 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705
  1. import collections
  2. from argparse import Action, ArgumentParser
  3. from borgmatic.config import collect
  4. SUBPARSER_ALIASES = {
  5. 'init': ['--init', '-I'],
  6. 'prune': ['--prune', '-p'],
  7. 'compact': [],
  8. 'create': ['--create', '-C'],
  9. 'check': ['--check', '-k'],
  10. 'extract': ['--extract', '-x'],
  11. 'export-tar': ['--export-tar'],
  12. 'mount': ['--mount', '-m'],
  13. 'umount': ['--umount', '-u'],
  14. 'restore': ['--restore', '-r'],
  15. 'list': ['--list', '-l'],
  16. 'info': ['--info', '-i'],
  17. 'borg': [],
  18. }
  19. def parse_subparser_arguments(unparsed_arguments, subparsers):
  20. '''
  21. Given a sequence of arguments and a dict from subparser name to argparse.ArgumentParser
  22. instance, give each requested action's subparser a shot at parsing all arguments. This allows
  23. common arguments like "--repository" to be shared across multiple subparsers.
  24. Return the result as a tuple of (a dict mapping from subparser name to a parsed namespace of
  25. arguments, a list of remaining arguments not claimed by any subparser).
  26. '''
  27. arguments = collections.OrderedDict()
  28. remaining_arguments = list(unparsed_arguments)
  29. alias_to_subparser_name = {
  30. alias: subparser_name
  31. for subparser_name, aliases in SUBPARSER_ALIASES.items()
  32. for alias in aliases
  33. }
  34. # If the "borg" action is used, skip all other subparsers. This avoids confusion like
  35. # "borg list" triggering borgmatic's own list action.
  36. if 'borg' in unparsed_arguments:
  37. subparsers = {'borg': subparsers['borg']}
  38. for subparser_name, subparser in subparsers.items():
  39. if subparser_name not in remaining_arguments:
  40. continue
  41. canonical_name = alias_to_subparser_name.get(subparser_name, subparser_name)
  42. # If a parsed value happens to be the same as the name of a subparser, remove it from the
  43. # remaining arguments. This prevents, for instance, "check --only extract" from triggering
  44. # the "extract" subparser.
  45. parsed, unused_remaining = subparser.parse_known_args(unparsed_arguments)
  46. for value in vars(parsed).values():
  47. if isinstance(value, str):
  48. if value in subparsers:
  49. remaining_arguments.remove(value)
  50. elif isinstance(value, list):
  51. for item in value:
  52. if item in subparsers:
  53. remaining_arguments.remove(item)
  54. arguments[canonical_name] = parsed
  55. # If no actions are explicitly requested, assume defaults: prune, compact, create, and check.
  56. if not arguments and '--help' not in unparsed_arguments and '-h' not in unparsed_arguments:
  57. for subparser_name in ('prune', 'compact', 'create', 'check'):
  58. subparser = subparsers[subparser_name]
  59. parsed, unused_remaining = subparser.parse_known_args(unparsed_arguments)
  60. arguments[subparser_name] = parsed
  61. remaining_arguments = list(unparsed_arguments)
  62. # Now ask each subparser, one by one, to greedily consume arguments.
  63. for subparser_name, subparser in subparsers.items():
  64. if subparser_name not in arguments.keys():
  65. continue
  66. subparser = subparsers[subparser_name]
  67. unused_parsed, remaining_arguments = subparser.parse_known_args(remaining_arguments)
  68. # Special case: If "borg" is present in the arguments, consume all arguments after (+1) the
  69. # "borg" action.
  70. if 'borg' in arguments:
  71. borg_options_index = remaining_arguments.index('borg') + 1
  72. arguments['borg'].options = remaining_arguments[borg_options_index:]
  73. remaining_arguments = remaining_arguments[:borg_options_index]
  74. # Remove the subparser names themselves.
  75. for subparser_name, subparser in subparsers.items():
  76. if subparser_name in remaining_arguments:
  77. remaining_arguments.remove(subparser_name)
  78. return (arguments, remaining_arguments)
  79. class Extend_action(Action):
  80. '''
  81. An argparse action to support Python 3.8's "extend" action in older versions of Python.
  82. '''
  83. def __call__(self, parser, namespace, values, option_string=None):
  84. items = getattr(namespace, self.dest, None)
  85. if items:
  86. items.extend(values)
  87. else:
  88. setattr(namespace, self.dest, list(values))
  89. def make_parsers():
  90. '''
  91. Build a top-level parser and its subparsers and return them as a tuple.
  92. '''
  93. config_paths = collect.get_default_config_paths(expand_home=True)
  94. unexpanded_config_paths = collect.get_default_config_paths(expand_home=False)
  95. global_parser = ArgumentParser(add_help=False)
  96. global_parser.register('action', 'extend', Extend_action)
  97. global_group = global_parser.add_argument_group('global arguments')
  98. global_group.add_argument(
  99. '-c',
  100. '--config',
  101. nargs='*',
  102. dest='config_paths',
  103. default=config_paths,
  104. help='Configuration filenames or directories, defaults to: {}'.format(
  105. ' '.join(unexpanded_config_paths)
  106. ),
  107. )
  108. global_group.add_argument(
  109. '--excludes',
  110. dest='excludes_filename',
  111. help='Deprecated in favor of exclude_patterns within configuration',
  112. )
  113. global_group.add_argument(
  114. '-n',
  115. '--dry-run',
  116. dest='dry_run',
  117. action='store_true',
  118. help='Go through the motions, but do not actually write to any repositories',
  119. )
  120. global_group.add_argument(
  121. '-nc', '--no-color', dest='no_color', action='store_true', help='Disable colored output'
  122. )
  123. global_group.add_argument(
  124. '-v',
  125. '--verbosity',
  126. type=int,
  127. choices=range(-1, 3),
  128. default=0,
  129. help='Display verbose progress to the console (from only errors to very verbose: -1, 0, 1, or 2)',
  130. )
  131. global_group.add_argument(
  132. '--syslog-verbosity',
  133. type=int,
  134. choices=range(-1, 3),
  135. default=0,
  136. help='Log verbose progress to syslog (from only errors to very verbose: -1, 0, 1, or 2). Ignored when console is interactive or --log-file is given',
  137. )
  138. global_group.add_argument(
  139. '--log-file-verbosity',
  140. type=int,
  141. choices=range(-1, 3),
  142. default=0,
  143. help='Log verbose progress to log file (from only errors to very verbose: -1, 0, 1, or 2). Only used when --log-file is given',
  144. )
  145. global_group.add_argument(
  146. '--monitoring-verbosity',
  147. type=int,
  148. choices=range(-1, 3),
  149. default=0,
  150. help='Log verbose progress to monitoring integrations that support logging (from only errors to very verbose: -1, 0, 1, or 2)',
  151. )
  152. global_group.add_argument(
  153. '--log-file',
  154. type=str,
  155. default=None,
  156. help='Write log messages to this file instead of syslog',
  157. )
  158. global_group.add_argument(
  159. '--override',
  160. metavar='SECTION.OPTION=VALUE',
  161. nargs='+',
  162. dest='overrides',
  163. action='extend',
  164. help='One or more configuration file options to override with specified values',
  165. )
  166. global_group.add_argument(
  167. '--no-env',
  168. dest='resolve_env',
  169. action='store_false',
  170. help='Do not resolve environment variables in configuration file',
  171. )
  172. global_group.add_argument(
  173. '--bash-completion',
  174. default=False,
  175. action='store_true',
  176. help='Show bash completion script and exit',
  177. )
  178. global_group.add_argument(
  179. '--version',
  180. dest='version',
  181. default=False,
  182. action='store_true',
  183. help='Display installed version number of borgmatic and exit',
  184. )
  185. top_level_parser = ArgumentParser(
  186. description='''
  187. Simple, configuration-driven backup software for servers and workstations. If none of
  188. the action options are given, then borgmatic defaults to: prune, compact, create, and
  189. check.
  190. ''',
  191. parents=[global_parser],
  192. )
  193. subparsers = top_level_parser.add_subparsers(
  194. title='actions',
  195. metavar='',
  196. help='Specify zero or more actions. Defaults to prune, compact, create, and check. Use --help with action for details:',
  197. )
  198. init_parser = subparsers.add_parser(
  199. 'init',
  200. aliases=SUBPARSER_ALIASES['init'],
  201. help='Initialize an empty Borg repository',
  202. description='Initialize an empty Borg repository',
  203. add_help=False,
  204. )
  205. init_group = init_parser.add_argument_group('init arguments')
  206. init_group.add_argument(
  207. '-e',
  208. '--encryption',
  209. dest='encryption_mode',
  210. help='Borg repository encryption mode',
  211. required=True,
  212. )
  213. init_group.add_argument(
  214. '--append-only',
  215. dest='append_only',
  216. action='store_true',
  217. help='Create an append-only repository',
  218. )
  219. init_group.add_argument(
  220. '--storage-quota',
  221. dest='storage_quota',
  222. help='Create a repository with a fixed storage quota',
  223. )
  224. init_group.add_argument('-h', '--help', action='help', help='Show this help message and exit')
  225. prune_parser = subparsers.add_parser(
  226. 'prune',
  227. aliases=SUBPARSER_ALIASES['prune'],
  228. help='Prune archives according to the retention policy (with Borg 1.2+, run compact afterwards to actually free space)',
  229. description='Prune archives according to the retention policy (with Borg 1.2+, run compact afterwards to actually free space)',
  230. add_help=False,
  231. )
  232. prune_group = prune_parser.add_argument_group('prune arguments')
  233. prune_group.add_argument(
  234. '--stats',
  235. dest='stats',
  236. default=False,
  237. action='store_true',
  238. help='Display statistics of archive',
  239. )
  240. prune_group.add_argument(
  241. '--files', dest='files', default=False, action='store_true', help='Show per-file details'
  242. )
  243. prune_group.add_argument('-h', '--help', action='help', help='Show this help message and exit')
  244. compact_parser = subparsers.add_parser(
  245. 'compact',
  246. aliases=SUBPARSER_ALIASES['compact'],
  247. help='Compact segments to free space (Borg 1.2+ only)',
  248. description='Compact segments to free space (Borg 1.2+ only)',
  249. add_help=False,
  250. )
  251. compact_group = compact_parser.add_argument_group('compact arguments')
  252. compact_group.add_argument(
  253. '--progress',
  254. dest='progress',
  255. default=False,
  256. action='store_true',
  257. help='Display progress as each segment is compacted',
  258. )
  259. compact_group.add_argument(
  260. '--cleanup-commits',
  261. dest='cleanup_commits',
  262. default=False,
  263. action='store_true',
  264. help='Cleanup commit-only 17-byte segment files left behind by Borg 1.1',
  265. )
  266. compact_group.add_argument(
  267. '--threshold',
  268. type=int,
  269. dest='threshold',
  270. help='Minimum saved space percentage threshold for compacting a segment, defaults to 10',
  271. )
  272. compact_group.add_argument(
  273. '-h', '--help', action='help', help='Show this help message and exit'
  274. )
  275. create_parser = subparsers.add_parser(
  276. 'create',
  277. aliases=SUBPARSER_ALIASES['create'],
  278. help='Create archives (actually perform backups)',
  279. description='Create archives (actually perform backups)',
  280. add_help=False,
  281. )
  282. create_group = create_parser.add_argument_group('create arguments')
  283. create_group.add_argument(
  284. '--progress',
  285. dest='progress',
  286. default=False,
  287. action='store_true',
  288. help='Display progress for each file as it is backed up',
  289. )
  290. create_group.add_argument(
  291. '--stats',
  292. dest='stats',
  293. default=False,
  294. action='store_true',
  295. help='Display statistics of archive',
  296. )
  297. create_group.add_argument(
  298. '--files', dest='files', default=False, action='store_true', help='Show per-file details'
  299. )
  300. create_group.add_argument(
  301. '--json', dest='json', default=False, action='store_true', help='Output results as JSON'
  302. )
  303. create_group.add_argument('-h', '--help', action='help', help='Show this help message and exit')
  304. check_parser = subparsers.add_parser(
  305. 'check',
  306. aliases=SUBPARSER_ALIASES['check'],
  307. help='Check archives for consistency',
  308. description='Check archives for consistency',
  309. add_help=False,
  310. )
  311. check_group = check_parser.add_argument_group('check arguments')
  312. check_group.add_argument(
  313. '--progress',
  314. dest='progress',
  315. default=False,
  316. action='store_true',
  317. help='Display progress for each file as it is checked',
  318. )
  319. check_group.add_argument(
  320. '--repair',
  321. dest='repair',
  322. default=False,
  323. action='store_true',
  324. help='Attempt to repair any inconsistencies found (for interactive use)',
  325. )
  326. check_group.add_argument(
  327. '--only',
  328. metavar='CHECK',
  329. choices=('repository', 'archives', 'data', 'extract'),
  330. dest='only',
  331. action='append',
  332. help='Run a particular consistency check (repository, archives, data, or extract) instead of configured checks (subject to configured frequency, can specify flag multiple times)',
  333. )
  334. check_group.add_argument(
  335. '--force',
  336. default=False,
  337. action='store_true',
  338. help='Ignore configured check frequencies and run checks unconditionally',
  339. )
  340. check_group.add_argument('-h', '--help', action='help', help='Show this help message and exit')
  341. extract_parser = subparsers.add_parser(
  342. 'extract',
  343. aliases=SUBPARSER_ALIASES['extract'],
  344. help='Extract files from a named archive to the current directory',
  345. description='Extract a named archive to the current directory',
  346. add_help=False,
  347. )
  348. extract_group = extract_parser.add_argument_group('extract arguments')
  349. extract_group.add_argument(
  350. '--repository',
  351. help='Path of repository to extract, defaults to the configured repository if there is only one',
  352. )
  353. extract_group.add_argument(
  354. '--archive', help='Name of archive to extract (or "latest")', required=True
  355. )
  356. extract_group.add_argument(
  357. '--path',
  358. '--restore-path',
  359. metavar='PATH',
  360. nargs='+',
  361. dest='paths',
  362. help='Paths to extract from archive, defaults to the entire archive',
  363. )
  364. extract_group.add_argument(
  365. '--destination',
  366. metavar='PATH',
  367. dest='destination',
  368. help='Directory to extract files into, defaults to the current directory',
  369. )
  370. extract_group.add_argument(
  371. '--strip-components',
  372. type=int,
  373. metavar='NUMBER',
  374. dest='strip_components',
  375. help='Number of leading path components to remove from each extracted path. Skip paths with fewer elements',
  376. )
  377. extract_group.add_argument(
  378. '--progress',
  379. dest='progress',
  380. default=False,
  381. action='store_true',
  382. help='Display progress for each file as it is extracted',
  383. )
  384. extract_group.add_argument(
  385. '-h', '--help', action='help', help='Show this help message and exit'
  386. )
  387. export_tar_parser = subparsers.add_parser(
  388. 'export-tar',
  389. aliases=SUBPARSER_ALIASES['export-tar'],
  390. help='Export an archive to a tar-formatted file or stream',
  391. description='Export an archive to a tar-formatted file or stream',
  392. add_help=False,
  393. )
  394. export_tar_group = export_tar_parser.add_argument_group('export-tar arguments')
  395. export_tar_group.add_argument(
  396. '--repository',
  397. help='Path of repository to export from, defaults to the configured repository if there is only one',
  398. )
  399. export_tar_group.add_argument(
  400. '--archive', help='Name of archive to export (or "latest")', required=True
  401. )
  402. export_tar_group.add_argument(
  403. '--path',
  404. metavar='PATH',
  405. nargs='+',
  406. dest='paths',
  407. help='Paths to export from archive, defaults to the entire archive',
  408. )
  409. export_tar_group.add_argument(
  410. '--destination',
  411. metavar='PATH',
  412. dest='destination',
  413. help='Path to destination export tar file, or "-" for stdout (but be careful about dirtying output with --verbosity or --files)',
  414. required=True,
  415. )
  416. export_tar_group.add_argument(
  417. '--tar-filter', help='Name of filter program to pipe data through'
  418. )
  419. export_tar_group.add_argument(
  420. '--files', default=False, action='store_true', help='Show per-file details'
  421. )
  422. export_tar_group.add_argument(
  423. '--strip-components',
  424. type=int,
  425. metavar='NUMBER',
  426. dest='strip_components',
  427. help='Number of leading path components to remove from each exported path. Skip paths with fewer elements',
  428. )
  429. export_tar_group.add_argument(
  430. '-h', '--help', action='help', help='Show this help message and exit'
  431. )
  432. mount_parser = subparsers.add_parser(
  433. 'mount',
  434. aliases=SUBPARSER_ALIASES['mount'],
  435. help='Mount files from a named archive as a FUSE filesystem',
  436. description='Mount a named archive as a FUSE filesystem',
  437. add_help=False,
  438. )
  439. mount_group = mount_parser.add_argument_group('mount arguments')
  440. mount_group.add_argument(
  441. '--repository',
  442. help='Path of repository to use, defaults to the configured repository if there is only one',
  443. )
  444. mount_group.add_argument('--archive', help='Name of archive to mount (or "latest")')
  445. mount_group.add_argument(
  446. '--mount-point',
  447. metavar='PATH',
  448. dest='mount_point',
  449. help='Path where filesystem is to be mounted',
  450. required=True,
  451. )
  452. mount_group.add_argument(
  453. '--path',
  454. metavar='PATH',
  455. nargs='+',
  456. dest='paths',
  457. help='Paths to mount from archive, defaults to the entire archive',
  458. )
  459. mount_group.add_argument(
  460. '--foreground',
  461. dest='foreground',
  462. default=False,
  463. action='store_true',
  464. help='Stay in foreground until ctrl-C is pressed',
  465. )
  466. mount_group.add_argument('--options', dest='options', help='Extra Borg mount options')
  467. mount_group.add_argument('-h', '--help', action='help', help='Show this help message and exit')
  468. umount_parser = subparsers.add_parser(
  469. 'umount',
  470. aliases=SUBPARSER_ALIASES['umount'],
  471. help='Unmount a FUSE filesystem that was mounted with "borgmatic mount"',
  472. description='Unmount a mounted FUSE filesystem',
  473. add_help=False,
  474. )
  475. umount_group = umount_parser.add_argument_group('umount arguments')
  476. umount_group.add_argument(
  477. '--mount-point',
  478. metavar='PATH',
  479. dest='mount_point',
  480. help='Path of filesystem to unmount',
  481. required=True,
  482. )
  483. umount_group.add_argument('-h', '--help', action='help', help='Show this help message and exit')
  484. restore_parser = subparsers.add_parser(
  485. 'restore',
  486. aliases=SUBPARSER_ALIASES['restore'],
  487. help='Restore database dumps from a named archive',
  488. description='Restore database dumps from a named archive. (To extract files instead, use "borgmatic extract".)',
  489. add_help=False,
  490. )
  491. restore_group = restore_parser.add_argument_group('restore arguments')
  492. restore_group.add_argument(
  493. '--repository',
  494. help='Path of repository to restore from, defaults to the configured repository if there is only one',
  495. )
  496. restore_group.add_argument(
  497. '--archive', help='Name of archive to restore from (or "latest")', required=True
  498. )
  499. restore_group.add_argument(
  500. '--database',
  501. metavar='NAME',
  502. nargs='+',
  503. dest='databases',
  504. help='Names of databases to restore from archive, defaults to all databases. Note that any databases to restore must be defined in borgmatic\'s configuration',
  505. )
  506. restore_group.add_argument(
  507. '-h', '--help', action='help', help='Show this help message and exit'
  508. )
  509. list_parser = subparsers.add_parser(
  510. 'list',
  511. aliases=SUBPARSER_ALIASES['list'],
  512. help='List archives',
  513. description='List archives or the contents of an archive',
  514. add_help=False,
  515. )
  516. list_group = list_parser.add_argument_group('list arguments')
  517. list_group.add_argument(
  518. '--repository', help='Path of repository to list, defaults to the configured repositories',
  519. )
  520. list_group.add_argument('--archive', help='Name of archive to list (or "latest")')
  521. list_group.add_argument(
  522. '--path',
  523. metavar='PATH',
  524. nargs='+',
  525. dest='paths',
  526. help='Paths or patterns to list from a single selected archive (via "--archive"), defaults to listing the entire archive',
  527. )
  528. list_group.add_argument(
  529. '--find',
  530. metavar='PATH',
  531. nargs='+',
  532. dest='find_paths',
  533. help='Partial paths or patterns to search for and list across multiple archives',
  534. )
  535. list_group.add_argument(
  536. '--short', default=False, action='store_true', help='Output only archive or path names'
  537. )
  538. list_group.add_argument('--format', help='Format for file listing')
  539. list_group.add_argument(
  540. '--json', default=False, action='store_true', help='Output results as JSON'
  541. )
  542. list_group.add_argument(
  543. '-P', '--prefix', help='Only list archive names starting with this prefix'
  544. )
  545. list_group.add_argument(
  546. '-a', '--glob-archives', metavar='GLOB', help='Only list archive names matching this glob'
  547. )
  548. list_group.add_argument(
  549. '--successful',
  550. default=True,
  551. action='store_true',
  552. help='Deprecated in favor of listing successful (non-checkpoint) backups by default in newer versions of Borg',
  553. )
  554. list_group.add_argument(
  555. '--sort-by', metavar='KEYS', help='Comma-separated list of sorting keys'
  556. )
  557. list_group.add_argument(
  558. '--first', metavar='N', help='List first N archives after other filters are applied'
  559. )
  560. list_group.add_argument(
  561. '--last', metavar='N', help='List last N archives after other filters are applied'
  562. )
  563. list_group.add_argument(
  564. '-e', '--exclude', metavar='PATTERN', help='Exclude paths matching the pattern'
  565. )
  566. list_group.add_argument(
  567. '--exclude-from', metavar='FILENAME', help='Exclude paths from exclude file, one per line'
  568. )
  569. list_group.add_argument('--pattern', help='Include or exclude paths matching a pattern')
  570. list_group.add_argument(
  571. '--patterns-from',
  572. metavar='FILENAME',
  573. help='Include or exclude paths matching patterns from pattern file, one per line',
  574. )
  575. list_group.add_argument('-h', '--help', action='help', help='Show this help message and exit')
  576. info_parser = subparsers.add_parser(
  577. 'info',
  578. aliases=SUBPARSER_ALIASES['info'],
  579. help='Display summary information on archives',
  580. description='Display summary information on archives',
  581. add_help=False,
  582. )
  583. info_group = info_parser.add_argument_group('info arguments')
  584. info_group.add_argument(
  585. '--repository',
  586. help='Path of repository to show info for, defaults to the configured repository if there is only one',
  587. )
  588. info_group.add_argument('--archive', help='Name of archive to show info for (or "latest")')
  589. info_group.add_argument(
  590. '--json', dest='json', default=False, action='store_true', help='Output results as JSON'
  591. )
  592. info_group.add_argument(
  593. '-P', '--prefix', help='Only show info for archive names starting with this prefix'
  594. )
  595. info_group.add_argument(
  596. '-a',
  597. '--glob-archives',
  598. metavar='GLOB',
  599. help='Only show info for archive names matching this glob',
  600. )
  601. info_group.add_argument(
  602. '--sort-by', metavar='KEYS', help='Comma-separated list of sorting keys'
  603. )
  604. info_group.add_argument(
  605. '--first',
  606. metavar='N',
  607. help='Show info for first N archives after other filters are applied',
  608. )
  609. info_group.add_argument(
  610. '--last', metavar='N', help='Show info for last N archives after other filters are applied'
  611. )
  612. info_group.add_argument('-h', '--help', action='help', help='Show this help message and exit')
  613. borg_parser = subparsers.add_parser(
  614. 'borg',
  615. aliases=SUBPARSER_ALIASES['borg'],
  616. help='Run an arbitrary Borg command',
  617. description='Run an arbitrary Borg command based on borgmatic\'s configuration',
  618. add_help=False,
  619. )
  620. borg_group = borg_parser.add_argument_group('borg arguments')
  621. borg_group.add_argument(
  622. '--repository',
  623. help='Path of repository to pass to Borg, defaults to the configured repositories',
  624. )
  625. borg_group.add_argument('--archive', help='Name of archive to pass to Borg (or "latest")')
  626. borg_group.add_argument(
  627. '--',
  628. metavar='OPTION',
  629. dest='options',
  630. nargs='+',
  631. help='Options to pass to Borg, command first ("create", "list", etc). "--" is optional. To specify the repository or the archive, you must use --repository or --archive instead of providing them here.',
  632. )
  633. borg_group.add_argument('-h', '--help', action='help', help='Show this help message and exit')
  634. return top_level_parser, subparsers
  635. def parse_arguments(*unparsed_arguments):
  636. '''
  637. Given command-line arguments with which this script was invoked, parse the arguments and return
  638. them as a dict mapping from subparser name (or "global") to an argparse.Namespace instance.
  639. '''
  640. top_level_parser, subparsers = make_parsers()
  641. arguments, remaining_arguments = parse_subparser_arguments(
  642. unparsed_arguments, subparsers.choices
  643. )
  644. arguments['global'] = top_level_parser.parse_args(remaining_arguments)
  645. if arguments['global'].excludes_filename:
  646. raise ValueError(
  647. 'The --excludes option has been replaced with exclude_patterns in configuration'
  648. )
  649. if 'init' in arguments and arguments['global'].dry_run:
  650. raise ValueError('The init action cannot be used with the --dry-run option')
  651. if (
  652. 'list' in arguments
  653. and 'info' in arguments
  654. and arguments['list'].json
  655. and arguments['info'].json
  656. ):
  657. raise ValueError('With the --json option, list and info actions cannot be used together')
  658. return arguments