Browse Source

borg delete --force --force to delete severely corrupted archives, fixes #1975

(cherry picked from commit 4d81b186ec5b64585bac1097b28b987a9f4bce15)
Thomas Waldmann 8 years ago
parent
commit
be1227fd1c
3 changed files with 35 additions and 6 deletions
  1. 3 3
      borg/archive.py
  2. 18 3
      borg/archiver.py
  3. 14 0
      borg/testsuite/archiver.py

+ 3 - 3
borg/archive.py

@@ -542,7 +542,7 @@ Number of files: {0.stats.nfiles}'''.format(
                 raise ChunksIndexError(cid)
                 raise ChunksIndexError(cid)
             except Repository.ObjectNotFound as e:
             except Repository.ObjectNotFound as e:
                 # object not in repo - strange, but we wanted to delete it anyway.
                 # object not in repo - strange, but we wanted to delete it anyway.
-                if not forced:
+                if forced == 0:
                     raise
                     raise
                 error = True
                 error = True
 
 
@@ -564,14 +564,14 @@ Number of files: {0.stats.nfiles}'''.format(
                 except (TypeError, ValueError):
                 except (TypeError, ValueError):
                     # if items metadata spans multiple chunks and one chunk got dropped somehow,
                     # if items metadata spans multiple chunks and one chunk got dropped somehow,
                     # it could be that unpacker yields bad types
                     # it could be that unpacker yields bad types
-                    if not forced:
+                    if forced == 0:
                         raise
                         raise
                     error = True
                     error = True
             if progress:
             if progress:
                 pi.finish()
                 pi.finish()
         except (msgpack.UnpackException, Repository.ObjectNotFound):
         except (msgpack.UnpackException, Repository.ObjectNotFound):
             # items metadata corrupted
             # items metadata corrupted
-            if not forced:
+            if forced == 0:
                 raise
                 raise
             error = True
             error = True
         # in forced delete mode, we try hard to delete at least the manifest entry,
         # in forced delete mode, we try hard to delete at least the manifest entry,

+ 18 - 3
borg/archiver.py

@@ -503,9 +503,23 @@ class Archiver:
     def do_delete(self, args, repository):
     def do_delete(self, args, repository):
         """Delete an existing repository or archive"""
         """Delete an existing repository or archive"""
         if args.location.archive:
         if args.location.archive:
+            archive_name = args.location.archive
             manifest, key = Manifest.load(repository)
             manifest, key = Manifest.load(repository)
+
+            if args.forced == 2:
+                try:
+                    del manifest.archives[archive_name]
+                except KeyError:
+                    raise Archive.DoesNotExist(archive_name)
+                logger.info('Archive deleted.')
+                manifest.write()
+                # note: might crash in compact() after committing the repo
+                repository.commit()
+                logger.info('Done. Run "borg check --repair" to clean up the mess.')
+                return self.exit_code
+
             with Cache(repository, key, manifest, lock_wait=self.lock_wait) as cache:
             with Cache(repository, key, manifest, lock_wait=self.lock_wait) as cache:
-                archive = Archive(repository, key, manifest, args.location.archive, cache=cache)
+                archive = Archive(repository, key, manifest, archive_name, cache=cache)
                 stats = Statistics()
                 stats = Statistics()
                 archive.delete(stats, progress=args.progress, forced=args.forced)
                 archive.delete(stats, progress=args.progress, forced=args.forced)
                 manifest.write()
                 manifest.write()
@@ -1554,8 +1568,9 @@ class Archiver:
                                action='store_true', default=False,
                                action='store_true', default=False,
                                help='delete only the local cache for the given repository')
                                help='delete only the local cache for the given repository')
         subparser.add_argument('--force', dest='forced',
         subparser.add_argument('--force', dest='forced',
-                               action='store_true', default=False,
-                               help='force deletion of corrupted archives')
+                               action='count', default=0,
+                               help='force deletion of corrupted archives, '
+                                    'use --force --force in case --force does not work.')
         subparser.add_argument('--save-space', dest='save_space', action='store_true',
         subparser.add_argument('--save-space', dest='save_space', action='store_true',
                                default=False,
                                default=False,
                                help='work slower, but using less space')
                                help='work slower, but using less space')

+ 14 - 0
borg/testsuite/archiver.py

@@ -914,6 +914,20 @@ class ArchiverTestCase(ArchiverTestCaseBase):
         # Make sure the repo is gone
         # Make sure the repo is gone
         self.assertFalse(os.path.exists(self.repository_path))
         self.assertFalse(os.path.exists(self.repository_path))
 
 
+    def test_delete_double_force(self):
+        self.cmd('init', '--encryption=none', self.repository_location)
+        self.create_src_archive('test')
+        with Repository(self.repository_path, exclusive=True) as repository:
+            manifest, key = Manifest.load(repository)
+            archive = Archive(repository, key, manifest, 'test')
+            id = archive.metadata[b'items'][0]
+            repository.put(id, b'corrupted items metadata stream chunk')
+            repository.commit()
+        self.cmd('delete', '--force', '--force', self.repository_location + '::test')
+        self.cmd('check', '--repair', self.repository_location)
+        output = self.cmd('list', self.repository_location)
+        self.assert_not_in('test', output)
+
     def test_corrupted_repository(self):
     def test_corrupted_repository(self):
         self.cmd('init', self.repository_location)
         self.cmd('init', self.repository_location)
         self.create_src_archive('test')
         self.create_src_archive('test')