arguments.py 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695
  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. '--bash-completion',
  168. default=False,
  169. action='store_true',
  170. help='Show bash completion script and exit',
  171. )
  172. global_group.add_argument(
  173. '--version',
  174. dest='version',
  175. default=False,
  176. action='store_true',
  177. help='Display installed version number of borgmatic and exit',
  178. )
  179. top_level_parser = ArgumentParser(
  180. description='''
  181. Simple, configuration-driven backup software for servers and workstations. If none of
  182. the action options are given, then borgmatic defaults to: prune, compact, create, and
  183. check.
  184. ''',
  185. parents=[global_parser],
  186. )
  187. subparsers = top_level_parser.add_subparsers(
  188. title='actions',
  189. metavar='',
  190. help='Specify zero or more actions. Defaults to prune, compact, create, and check. Use --help with action for details:',
  191. )
  192. init_parser = subparsers.add_parser(
  193. 'init',
  194. aliases=SUBPARSER_ALIASES['init'],
  195. help='Initialize an empty Borg repository',
  196. description='Initialize an empty Borg repository',
  197. add_help=False,
  198. )
  199. init_group = init_parser.add_argument_group('init arguments')
  200. init_group.add_argument(
  201. '-e',
  202. '--encryption',
  203. dest='encryption_mode',
  204. help='Borg repository encryption mode',
  205. required=True,
  206. )
  207. init_group.add_argument(
  208. '--append-only',
  209. dest='append_only',
  210. action='store_true',
  211. help='Create an append-only repository',
  212. )
  213. init_group.add_argument(
  214. '--storage-quota',
  215. dest='storage_quota',
  216. help='Create a repository with a fixed storage quota',
  217. )
  218. init_group.add_argument('-h', '--help', action='help', help='Show this help message and exit')
  219. prune_parser = subparsers.add_parser(
  220. 'prune',
  221. aliases=SUBPARSER_ALIASES['prune'],
  222. help='Prune archives according to the retention policy (with Borg 1.2+, run compact afterwards to actually free space)',
  223. description='Prune archives according to the retention policy (with Borg 1.2+, run compact afterwards to actually free space)',
  224. add_help=False,
  225. )
  226. prune_group = prune_parser.add_argument_group('prune arguments')
  227. prune_group.add_argument(
  228. '--stats',
  229. dest='stats',
  230. default=False,
  231. action='store_true',
  232. help='Display statistics of archive',
  233. )
  234. prune_group.add_argument(
  235. '--files', dest='files', default=False, action='store_true', help='Show per-file details'
  236. )
  237. prune_group.add_argument('-h', '--help', action='help', help='Show this help message and exit')
  238. compact_parser = subparsers.add_parser(
  239. 'compact',
  240. aliases=SUBPARSER_ALIASES['compact'],
  241. help='Compact segments to free space (Borg 1.2+ only)',
  242. description='Compact segments to free space (Borg 1.2+ only)',
  243. add_help=False,
  244. )
  245. compact_group = compact_parser.add_argument_group('compact arguments')
  246. compact_group.add_argument(
  247. '--progress',
  248. dest='progress',
  249. default=False,
  250. action='store_true',
  251. help='Display progress as each segment is compacted',
  252. )
  253. compact_group.add_argument(
  254. '--cleanup-commits',
  255. dest='cleanup_commits',
  256. default=False,
  257. action='store_true',
  258. help='Cleanup commit-only 17-byte segment files left behind by Borg 1.1',
  259. )
  260. compact_group.add_argument(
  261. '--threshold',
  262. type=int,
  263. dest='threshold',
  264. help='Minimum saved space percentage threshold for compacting a segment, defaults to 10',
  265. )
  266. compact_group.add_argument(
  267. '-h', '--help', action='help', help='Show this help message and exit'
  268. )
  269. create_parser = subparsers.add_parser(
  270. 'create',
  271. aliases=SUBPARSER_ALIASES['create'],
  272. help='Create archives (actually perform backups)',
  273. description='Create archives (actually perform backups)',
  274. add_help=False,
  275. )
  276. create_group = create_parser.add_argument_group('create arguments')
  277. create_group.add_argument(
  278. '--progress',
  279. dest='progress',
  280. default=False,
  281. action='store_true',
  282. help='Display progress for each file as it is backed up',
  283. )
  284. create_group.add_argument(
  285. '--stats',
  286. dest='stats',
  287. default=False,
  288. action='store_true',
  289. help='Display statistics of archive',
  290. )
  291. create_group.add_argument(
  292. '--files', dest='files', default=False, action='store_true', help='Show per-file details'
  293. )
  294. create_group.add_argument(
  295. '--json', dest='json', default=False, action='store_true', help='Output results as JSON'
  296. )
  297. create_group.add_argument('-h', '--help', action='help', help='Show this help message and exit')
  298. check_parser = subparsers.add_parser(
  299. 'check',
  300. aliases=SUBPARSER_ALIASES['check'],
  301. help='Check archives for consistency',
  302. description='Check archives for consistency',
  303. add_help=False,
  304. )
  305. check_group = check_parser.add_argument_group('check arguments')
  306. check_group.add_argument(
  307. '--progress',
  308. dest='progress',
  309. default=False,
  310. action='store_true',
  311. help='Display progress for each file as it is checked',
  312. )
  313. check_group.add_argument(
  314. '--repair',
  315. dest='repair',
  316. default=False,
  317. action='store_true',
  318. help='Attempt to repair any inconsistencies found (for interactive use)',
  319. )
  320. check_group.add_argument(
  321. '--only',
  322. metavar='CHECK',
  323. choices=('repository', 'archives', 'data', 'extract'),
  324. dest='only',
  325. action='append',
  326. help='Run a particular consistency check (repository, archives, data, or extract) instead of configured checks (subject to configured frequency, can specify flag multiple times)',
  327. )
  328. check_group.add_argument(
  329. '--force',
  330. default=False,
  331. action='store_true',
  332. help='Ignore configured check frequencies and run checks unconditionally',
  333. )
  334. check_group.add_argument('-h', '--help', action='help', help='Show this help message and exit')
  335. extract_parser = subparsers.add_parser(
  336. 'extract',
  337. aliases=SUBPARSER_ALIASES['extract'],
  338. help='Extract files from a named archive to the current directory',
  339. description='Extract a named archive to the current directory',
  340. add_help=False,
  341. )
  342. extract_group = extract_parser.add_argument_group('extract arguments')
  343. extract_group.add_argument(
  344. '--repository',
  345. help='Path of repository to extract, defaults to the configured repository if there is only one',
  346. )
  347. extract_group.add_argument(
  348. '--archive', help='Name of archive to extract (or "latest")', required=True
  349. )
  350. extract_group.add_argument(
  351. '--path',
  352. '--restore-path',
  353. metavar='PATH',
  354. nargs='+',
  355. dest='paths',
  356. help='Paths to extract from archive, defaults to the entire archive',
  357. )
  358. extract_group.add_argument(
  359. '--destination',
  360. metavar='PATH',
  361. dest='destination',
  362. help='Directory to extract files into, defaults to the current directory',
  363. )
  364. extract_group.add_argument(
  365. '--strip-components',
  366. type=int,
  367. metavar='NUMBER',
  368. dest='strip_components',
  369. help='Number of leading path components to remove from each extracted path. Skip paths with fewer elements',
  370. )
  371. extract_group.add_argument(
  372. '--progress',
  373. dest='progress',
  374. default=False,
  375. action='store_true',
  376. help='Display progress for each file as it is extracted',
  377. )
  378. extract_group.add_argument(
  379. '-h', '--help', action='help', help='Show this help message and exit'
  380. )
  381. export_tar_parser = subparsers.add_parser(
  382. 'export-tar',
  383. aliases=SUBPARSER_ALIASES['export-tar'],
  384. help='Export an archive to a tar-formatted file or stream',
  385. description='Export an archive to a tar-formatted file or stream',
  386. add_help=False,
  387. )
  388. export_tar_group = export_tar_parser.add_argument_group('export-tar arguments')
  389. export_tar_group.add_argument(
  390. '--repository',
  391. help='Path of repository to export from, defaults to the configured repository if there is only one',
  392. )
  393. export_tar_group.add_argument(
  394. '--archive', help='Name of archive to export (or "latest")', required=True
  395. )
  396. export_tar_group.add_argument(
  397. '--path',
  398. metavar='PATH',
  399. nargs='+',
  400. dest='paths',
  401. help='Paths to export from archive, defaults to the entire archive',
  402. )
  403. export_tar_group.add_argument(
  404. '--destination',
  405. metavar='PATH',
  406. dest='destination',
  407. help='Path to destination export tar file, or "-" for stdout (but be careful about dirtying output with --verbosity or --files)',
  408. required=True,
  409. )
  410. export_tar_group.add_argument(
  411. '--tar-filter', help='Name of filter program to pipe data through'
  412. )
  413. export_tar_group.add_argument(
  414. '--files', default=False, action='store_true', help='Show per-file details'
  415. )
  416. export_tar_group.add_argument(
  417. '--strip-components',
  418. type=int,
  419. metavar='NUMBER',
  420. dest='strip_components',
  421. help='Number of leading path components to remove from each exported path. Skip paths with fewer elements',
  422. )
  423. export_tar_group.add_argument(
  424. '-h', '--help', action='help', help='Show this help message and exit'
  425. )
  426. mount_parser = subparsers.add_parser(
  427. 'mount',
  428. aliases=SUBPARSER_ALIASES['mount'],
  429. help='Mount files from a named archive as a FUSE filesystem',
  430. description='Mount a named archive as a FUSE filesystem',
  431. add_help=False,
  432. )
  433. mount_group = mount_parser.add_argument_group('mount arguments')
  434. mount_group.add_argument(
  435. '--repository',
  436. help='Path of repository to use, defaults to the configured repository if there is only one',
  437. )
  438. mount_group.add_argument('--archive', help='Name of archive to mount (or "latest")')
  439. mount_group.add_argument(
  440. '--mount-point',
  441. metavar='PATH',
  442. dest='mount_point',
  443. help='Path where filesystem is to be mounted',
  444. required=True,
  445. )
  446. mount_group.add_argument(
  447. '--path',
  448. metavar='PATH',
  449. nargs='+',
  450. dest='paths',
  451. help='Paths to mount from archive, defaults to the entire archive',
  452. )
  453. mount_group.add_argument(
  454. '--foreground',
  455. dest='foreground',
  456. default=False,
  457. action='store_true',
  458. help='Stay in foreground until ctrl-C is pressed',
  459. )
  460. mount_group.add_argument('--options', dest='options', help='Extra Borg mount options')
  461. mount_group.add_argument('-h', '--help', action='help', help='Show this help message and exit')
  462. umount_parser = subparsers.add_parser(
  463. 'umount',
  464. aliases=SUBPARSER_ALIASES['umount'],
  465. help='Unmount a FUSE filesystem that was mounted with "borgmatic mount"',
  466. description='Unmount a mounted FUSE filesystem',
  467. add_help=False,
  468. )
  469. umount_group = umount_parser.add_argument_group('umount arguments')
  470. umount_group.add_argument(
  471. '--mount-point',
  472. metavar='PATH',
  473. dest='mount_point',
  474. help='Path of filesystem to unmount',
  475. required=True,
  476. )
  477. umount_group.add_argument('-h', '--help', action='help', help='Show this help message and exit')
  478. restore_parser = subparsers.add_parser(
  479. 'restore',
  480. aliases=SUBPARSER_ALIASES['restore'],
  481. help='Restore database dumps from a named archive',
  482. description='Restore database dumps from a named archive. (To extract files instead, use "borgmatic extract".)',
  483. add_help=False,
  484. )
  485. restore_group = restore_parser.add_argument_group('restore arguments')
  486. restore_group.add_argument(
  487. '--repository',
  488. help='Path of repository to restore from, defaults to the configured repository if there is only one',
  489. )
  490. restore_group.add_argument(
  491. '--archive', help='Name of archive to restore from (or "latest")', required=True
  492. )
  493. restore_group.add_argument(
  494. '--database',
  495. metavar='NAME',
  496. nargs='+',
  497. dest='databases',
  498. 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',
  499. )
  500. restore_group.add_argument(
  501. '-h', '--help', action='help', help='Show this help message and exit'
  502. )
  503. list_parser = subparsers.add_parser(
  504. 'list',
  505. aliases=SUBPARSER_ALIASES['list'],
  506. help='List archives',
  507. description='List archives or the contents of an archive',
  508. add_help=False,
  509. )
  510. list_group = list_parser.add_argument_group('list arguments')
  511. list_group.add_argument(
  512. '--repository', help='Path of repository to list, defaults to the configured repositories',
  513. )
  514. list_group.add_argument('--archive', help='Name of archive to list (or "latest")')
  515. list_group.add_argument(
  516. '--path',
  517. metavar='PATH',
  518. nargs='+',
  519. dest='paths',
  520. help='Paths to list from archive, defaults to the entire archive',
  521. )
  522. list_group.add_argument(
  523. '--short', default=False, action='store_true', help='Output only archive or path names'
  524. )
  525. list_group.add_argument('--format', help='Format for file listing')
  526. list_group.add_argument(
  527. '--json', default=False, action='store_true', help='Output results as JSON'
  528. )
  529. list_group.add_argument(
  530. '-P', '--prefix', help='Only list archive names starting with this prefix'
  531. )
  532. list_group.add_argument(
  533. '-a', '--glob-archives', metavar='GLOB', help='Only list archive names matching this glob'
  534. )
  535. list_group.add_argument(
  536. '--successful',
  537. default=False,
  538. action='store_true',
  539. help='Only list archive names of successful (non-checkpoint) backups',
  540. )
  541. list_group.add_argument(
  542. '--sort-by', metavar='KEYS', help='Comma-separated list of sorting keys'
  543. )
  544. list_group.add_argument(
  545. '--first', metavar='N', help='List first N archives after other filters are applied'
  546. )
  547. list_group.add_argument(
  548. '--last', metavar='N', help='List last N archives after other filters are applied'
  549. )
  550. list_group.add_argument(
  551. '-e', '--exclude', metavar='PATTERN', help='Exclude paths matching the pattern'
  552. )
  553. list_group.add_argument(
  554. '--exclude-from', metavar='FILENAME', help='Exclude paths from exclude file, one per line'
  555. )
  556. list_group.add_argument('--pattern', help='Include or exclude paths matching a pattern')
  557. list_group.add_argument(
  558. '--patterns-from',
  559. metavar='FILENAME',
  560. help='Include or exclude paths matching patterns from pattern file, one per line',
  561. )
  562. list_group.add_argument('-h', '--help', action='help', help='Show this help message and exit')
  563. info_parser = subparsers.add_parser(
  564. 'info',
  565. aliases=SUBPARSER_ALIASES['info'],
  566. help='Display summary information on archives',
  567. description='Display summary information on archives',
  568. add_help=False,
  569. )
  570. info_group = info_parser.add_argument_group('info arguments')
  571. info_group.add_argument(
  572. '--repository',
  573. help='Path of repository to show info for, defaults to the configured repository if there is only one',
  574. )
  575. info_group.add_argument('--archive', help='Name of archive to show info for (or "latest")')
  576. info_group.add_argument(
  577. '--json', dest='json', default=False, action='store_true', help='Output results as JSON'
  578. )
  579. info_group.add_argument(
  580. '-P', '--prefix', help='Only show info for archive names starting with this prefix'
  581. )
  582. info_group.add_argument(
  583. '-a',
  584. '--glob-archives',
  585. metavar='GLOB',
  586. help='Only show info for archive names matching this glob',
  587. )
  588. info_group.add_argument(
  589. '--sort-by', metavar='KEYS', help='Comma-separated list of sorting keys'
  590. )
  591. info_group.add_argument(
  592. '--first',
  593. metavar='N',
  594. help='Show info for first N archives after other filters are applied',
  595. )
  596. info_group.add_argument(
  597. '--last', metavar='N', help='Show info for last N archives after other filters are applied'
  598. )
  599. info_group.add_argument('-h', '--help', action='help', help='Show this help message and exit')
  600. borg_parser = subparsers.add_parser(
  601. 'borg',
  602. aliases=SUBPARSER_ALIASES['borg'],
  603. help='Run an arbitrary Borg command',
  604. description='Run an arbitrary Borg command based on borgmatic\'s configuration',
  605. add_help=False,
  606. )
  607. borg_group = borg_parser.add_argument_group('borg arguments')
  608. borg_group.add_argument(
  609. '--repository',
  610. help='Path of repository to pass to Borg, defaults to the configured repositories',
  611. )
  612. borg_group.add_argument('--archive', help='Name of archive to pass to Borg (or "latest")')
  613. borg_group.add_argument(
  614. '--',
  615. metavar='OPTION',
  616. dest='options',
  617. nargs='+',
  618. 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.',
  619. )
  620. borg_group.add_argument('-h', '--help', action='help', help='Show this help message and exit')
  621. return top_level_parser, subparsers
  622. def parse_arguments(*unparsed_arguments):
  623. '''
  624. Given command-line arguments with which this script was invoked, parse the arguments and return
  625. them as a dict mapping from subparser name (or "global") to an argparse.Namespace instance.
  626. '''
  627. top_level_parser, subparsers = make_parsers()
  628. arguments, remaining_arguments = parse_subparser_arguments(
  629. unparsed_arguments, subparsers.choices
  630. )
  631. arguments['global'] = top_level_parser.parse_args(remaining_arguments)
  632. if arguments['global'].excludes_filename:
  633. raise ValueError(
  634. 'The --excludes option has been replaced with exclude_patterns in configuration'
  635. )
  636. if 'init' in arguments and arguments['global'].dry_run:
  637. raise ValueError('The init action cannot be used with the --dry-run option')
  638. if 'list' in arguments and arguments['list'].glob_archives and arguments['list'].successful:
  639. raise ValueError('The --glob-archives and --successful options cannot be used together')
  640. if (
  641. 'list' in arguments
  642. and 'info' in arguments
  643. and arguments['list'].json
  644. and arguments['info'].json
  645. ):
  646. raise ValueError('With the --json option, list and info actions cannot be used together')
  647. return arguments