Răsfoiți Sursa

Fix an error about the runtime directory getting excluded by tweaking its logic and lowering the error to a warning (#1211).

Dan Helfman 1 zi în urmă
părinte
comite
ec8168af46
3 a modificat fișierele cu 97 adăugiri și 44 ștergeri
  1. 2 0
      NEWS
  2. 23 9
      borgmatic/borg/create.py
  3. 72 35
      tests/unit/borg/test_create.py

+ 2 - 0
NEWS

@@ -3,6 +3,8 @@
    "--database" flag specifies.
  * #1210: Fix an error when running the "spot" check or "extract" action with the "progress" option
    or "--progress" flag.
+ * #1211: Fix an error about the runtime directory getting excluded by tweaking its logic and
+   lowering the error to a warning.
  * #1212: Fix an error when restoring multiple directory-format database dumps at once.
 
 2.0.13

+ 23 - 9
borgmatic/borg/create.py

@@ -96,22 +96,36 @@ def validate_planned_backup_paths(
         if path_line and path_line.startswith(('- ', '+ '))
     )
 
+    include_pattern_paths = {
+        pattern.path
+        for pattern in patterns
+        if pattern.type == borgmatic.borg.pattern.Pattern_type.INCLUDE
+    }
     runtime_directory_root_patterns = tuple(
         pattern
         for pattern in patterns
         if any_parent_directories(pattern.path, (borgmatic_runtime_directory,))
         if pattern.type == borgmatic.borg.pattern.Pattern_type.ROOT
+        # Skip root patterns that have corresponding include patterns, because those will "punch
+        # through" any subsequent excludes.
+        if pattern.path not in include_pattern_paths
     )
 
-    if not dry_run and os.path.exists(borgmatic_runtime_directory):
-        # If there are any root patterns in the runtime directory that are missing from the paths
-        # Borg is planning to backup, then they must've gotten excluded, e.g. by user-configured
-        # excludes. Error accordingly.
-        for pattern in runtime_directory_root_patterns:
-            if not any(any_parent_directories(path, (pattern.path,)) for path in paths):
-                raise ValueError(
-                    f'The runtime directory {os.path.normpath(borgmatic_runtime_directory)} overlaps with the configured excludes or patterns with excludes. Please ensure the runtime directory is not excluded.',
-                )
+    # If all root patterns in the runtime directory are missing from the paths Borg is planning to
+    # backup, then they must've gotten excluded, e.g. by user-configured excludes. Warn accordingly.
+    if (
+        not dry_run
+        and os.path.exists(borgmatic_runtime_directory)
+        and runtime_directory_root_patterns
+        and not any(
+            any_parent_directories(path, (pattern.path,))
+            for pattern in runtime_directory_root_patterns
+            for path in paths
+        )
+    ):
+        logger.warning(
+            f'The runtime directory {os.path.normpath(borgmatic_runtime_directory)} overlaps with the configured excludes (or the snapshotted source directories are empty). Please ensure the runtime directory is not excluded.'
+        )
 
     # Return the subset of output paths *not* contained within the borgmatic runtime directory. The
     # intent is that any downstream checks using these paths should skip runtime paths that

+ 72 - 35
tests/unit/borg/test_create.py

@@ -121,7 +121,7 @@ def test_validate_planned_backup_paths_skips_borgmatic_runtime_directory():
     ) == ('/foo', '/baz')
 
 
-def test_validate_planned_backup_paths_with_borgmatic_runtime_directory_missing_from_paths_output_errors():
+def test_validate_planned_backup_paths_with_borgmatic_runtime_directory_missing_from_paths_output_warns():
     flexmock(module.flags).should_receive('omit_flag').replace_with(
         lambda arguments, flag: arguments,
     )
@@ -136,26 +136,26 @@ def test_validate_planned_backup_paths_with_borgmatic_runtime_directory_missing_
     flexmock(module).should_receive('any_parent_directories').replace_with(
         lambda path, candidates: any(path.startswith(parent) for parent in candidates)
     )
+    flexmock(module.logger).should_receive('warning').once()
 
-    with pytest.raises(ValueError):
-        module.validate_planned_backup_paths(
-            dry_run=False,
-            create_command=('borg', 'create'),
-            config={},
-            patterns=(
-                module.borgmatic.borg.pattern.Pattern('/foo'),
-                module.borgmatic.borg.pattern.Pattern(
-                    '/run/borgmatic/bar', module.borgmatic.borg.pattern.Pattern_type.ROOT
-                ),
-                module.borgmatic.borg.pattern.Pattern('/baz'),
+    assert module.validate_planned_backup_paths(
+        dry_run=False,
+        create_command=('borg', 'create'),
+        config={},
+        patterns=(
+            module.borgmatic.borg.pattern.Pattern('/foo'),
+            module.borgmatic.borg.pattern.Pattern(
+                '/run/borgmatic/bar', module.borgmatic.borg.pattern.Pattern_type.ROOT
             ),
-            local_path=None,
-            working_directory=None,
-            borgmatic_runtime_directory='/run/borgmatic',
-        )
+            module.borgmatic.borg.pattern.Pattern('/baz'),
+        ),
+        local_path=None,
+        working_directory=None,
+        borgmatic_runtime_directory='/run/borgmatic',
+    ) == ('/foo', '/bar', '/baz')
 
 
-def test_validate_planned_backup_paths_with_borgmatic_runtime_directory_partially_excluded_from_paths_output_errors():
+def test_validate_planned_backup_paths_with_borgmatic_runtime_directory_partially_excluded_from_paths_output_does_not_warn():
     flexmock(module.flags).should_receive('omit_flag').replace_with(
         lambda arguments, flag: arguments,
     )
@@ -172,26 +172,63 @@ def test_validate_planned_backup_paths_with_borgmatic_runtime_directory_partiall
     flexmock(module).should_receive('any_parent_directories').replace_with(
         lambda path, candidates: any(path.startswith(parent) for parent in candidates)
     )
+    flexmock(module.logger).should_receive('warning').never()
 
-    with pytest.raises(ValueError):
-        module.validate_planned_backup_paths(
-            dry_run=False,
-            create_command=('borg', 'create'),
-            config={},
-            patterns=(
-                module.borgmatic.borg.pattern.Pattern('/foo'),
-                module.borgmatic.borg.pattern.Pattern(
-                    '/run/borgmatic/bar', module.borgmatic.borg.pattern.Pattern_type.ROOT
-                ),
-                module.borgmatic.borg.pattern.Pattern('/baz'),
-                module.borgmatic.borg.pattern.Pattern(
-                    '/run/borgmatic/quux', module.borgmatic.borg.pattern.Pattern_type.ROOT
-                ),
+    assert module.validate_planned_backup_paths(
+        dry_run=False,
+        create_command=('borg', 'create'),
+        config={},
+        patterns=(
+            module.borgmatic.borg.pattern.Pattern('/foo'),
+            module.borgmatic.borg.pattern.Pattern(
+                '/run/borgmatic/bar', module.borgmatic.borg.pattern.Pattern_type.ROOT
             ),
-            local_path=None,
-            working_directory=None,
-            borgmatic_runtime_directory='/run/borgmatic',
-        )
+            module.borgmatic.borg.pattern.Pattern('/baz'),
+            module.borgmatic.borg.pattern.Pattern(
+                '/run/borgmatic/quux', module.borgmatic.borg.pattern.Pattern_type.ROOT
+            ),
+        ),
+        local_path=None,
+        working_directory=None,
+        borgmatic_runtime_directory='/run/borgmatic',
+    ) == ('/foo', '/baz')
+
+
+def test_validate_planned_backup_paths_with_borgmatic_runtime_directory_with_corresponding_include_and_missing_from_paths_output_does_not_warn():
+    flexmock(module.flags).should_receive('omit_flag').replace_with(
+        lambda arguments, flag: arguments,
+    )
+    flexmock(module.flags).should_receive('omit_flag_and_value').replace_with(
+        lambda arguments, flag: arguments,
+    )
+    flexmock(module.environment).should_receive('make_environment').and_return(None)
+    flexmock(module).should_receive('execute_command_and_capture_output').and_return(
+        '+ /foo\n- /bar\n- /baz',
+    )
+    flexmock(module.os.path).should_receive('exists').and_return(True)
+    flexmock(module).should_receive('any_parent_directories').replace_with(
+        lambda path, candidates: any(path.startswith(parent) for parent in candidates)
+    )
+    flexmock(module.logger).should_receive('warning').never()
+
+    assert module.validate_planned_backup_paths(
+        dry_run=False,
+        create_command=('borg', 'create'),
+        config={},
+        patterns=(
+            module.borgmatic.borg.pattern.Pattern('/foo'),
+            module.borgmatic.borg.pattern.Pattern(
+                '/run/borgmatic/bar', module.borgmatic.borg.pattern.Pattern_type.ROOT
+            ),
+            module.borgmatic.borg.pattern.Pattern(
+                '/run/borgmatic/bar', module.borgmatic.borg.pattern.Pattern_type.INCLUDE
+            ),
+            module.borgmatic.borg.pattern.Pattern('/baz'),
+        ),
+        local_path=None,
+        working_directory=None,
+        borgmatic_runtime_directory='/run/borgmatic',
+    ) == ('/foo', '/bar', '/baz')
 
 
 def test_validate_planned_backup_paths_with_borgmatic_runtime_directory_missing_from_patterns_does_not_raise():