Переглянути джерело

Attempt to repair any inconsistencies found during a consistency check via "borgmatic check --repair" flag (#266).

Dan Helfman 5 роки тому
батько
коміт
2ab9daaa0f

+ 2 - 0
NEWS

@@ -2,6 +2,8 @@
  * #235: Pass extra options directly to particular Borg commands, handy for Borg options that
    borgmatic does not yet support natively. Use "extra_borg_options" in the storage configuration
    section.
+ * #266: Attempt to repair any inconsistencies found during a consistency check via
+   "borgmatic check --repair" flag.
 
 1.4.16
  * #256: Fix for "before_backup" hook not triggering an error when the command contains "borg" and

+ 13 - 7
borgmatic/borg/check.py

@@ -1,7 +1,7 @@
 import logging
 
 from borgmatic.borg import extract
-from borgmatic.execute import execute_command
+from borgmatic.execute import execute_command, execute_command_without_capture
 
 DEFAULT_CHECKS = ('repository', 'archives')
 DEFAULT_PREFIX = '{hostname}-'
@@ -91,12 +91,13 @@ def check_archives(
     consistency_config,
     local_path='borg',
     remote_path=None,
+    repair=None,
     only_checks=None,
 ):
     '''
     Given a local or remote repository path, a storage config dict, a consistency config dict,
-    local/remote commands to run, and an optional list of checks to use instead of configured
-    checks, check the contained Borg archives for consistency.
+    local/remote commands to run, whether to attempt a repair, and an optional list of checks
+    to use instead of configured checks, check the contained Borg archives for consistency.
 
     If there are no consistency checks to run, skip running them.
     '''
@@ -106,9 +107,7 @@ def check_archives(
     extra_borg_options = storage_config.get('extra_borg_options', {}).get('check', '')
 
     if set(checks).intersection(set(DEFAULT_CHECKS + ('data',))):
-        remote_path_flags = ('--remote-path', remote_path) if remote_path else ()
         lock_wait = storage_config.get('lock_wait', None)
-        lock_wait_flags = ('--lock-wait', str(lock_wait)) if lock_wait else ()
 
         verbosity_flags = ()
         if logger.isEnabledFor(logging.INFO):
@@ -120,14 +119,21 @@ def check_archives(
 
         full_command = (
             (local_path, 'check')
+            + (('--repair',) if repair else ())
             + _make_check_flags(checks, check_last, prefix)
-            + remote_path_flags
-            + lock_wait_flags
+            + (('--remote-path', remote_path) if remote_path else ())
+            + (('--lock-wait', str(lock_wait)) if lock_wait else ())
             + verbosity_flags
             + (tuple(extra_borg_options.split(' ')) if extra_borg_options else ())
             + (repository,)
         )
 
+        # The Borg repair option trigger an interactive prompt, which won't work when output is
+        # captured.
+        if repair:
+            execute_command_without_capture(full_command, error_on_warnings=True)
+            return
+
         execute_command(full_command, error_on_warnings=True)
 
     if 'extract' in checks:

+ 7 - 0
borgmatic/commands/arguments.py

@@ -266,6 +266,13 @@ def parse_arguments(*unparsed_arguments):
         add_help=False,
     )
     check_group = check_parser.add_argument_group('check arguments')
+    check_group.add_argument(
+        '--repair',
+        dest='repair',
+        default=False,
+        action='store_true',
+        help='Attempt to repair any inconsistencies found (experimental and only for interactive use)',
+    )
     check_group.add_argument(
         '--only',
         metavar='CHECK',

+ 1 - 0
borgmatic/commands/borgmatic.py

@@ -230,6 +230,7 @@ def run_actions(
             consistency,
             local_path=local_path,
             remote_path=remote_path,
+            repair=arguments['check'].repair,
             only_checks=arguments['check'].only,
         )
     if 'extract' in arguments:

+ 15 - 0
tests/unit/borg/test_check.py

@@ -158,6 +158,21 @@ def test_make_check_flags_with_default_checks_and_prefix_includes_prefix_flag():
     assert flags == ('--prefix', 'foo-')
 
 
+def test_check_archives_with_repair_calls_borg_with_repair_parameter():
+    checks = ('repository',)
+    consistency_config = {'check_last': None}
+    flexmock(module).should_receive('_parse_checks').and_return(checks)
+    flexmock(module).should_receive('_make_check_flags').and_return(())
+    flexmock(module).should_receive('execute_command').never()
+    flexmock(module).should_receive('execute_command_without_capture').with_args(
+        ('borg', 'check', '--repair', 'repo'), error_on_warnings=True
+    ).once()
+
+    module.check_archives(
+        repository='repo', storage_config={}, consistency_config=consistency_config, repair=True
+    )
+
+
 @pytest.mark.parametrize(
     'checks',
     (