arguments.py 29 KB

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