arguments.py 34 KB

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