From be1227fd1cb0b048c5026d71002c6aea9bc01792 Mon Sep 17 00:00:00 2001 From: Thomas Waldmann Date: Sun, 19 Feb 2017 07:07:12 +0100 Subject: [PATCH] borg delete --force --force to delete severely corrupted archives, fixes #1975 (cherry picked from commit 4d81b186ec5b64585bac1097b28b987a9f4bce15) --- borg/archive.py | 6 +++--- borg/archiver.py | 21 ++++++++++++++++++--- borg/testsuite/archiver.py | 14 ++++++++++++++ 3 files changed, 35 insertions(+), 6 deletions(-) diff --git a/borg/archive.py b/borg/archive.py index 094a09c4c..8c1eabcf7 100644 --- a/borg/archive.py +++ b/borg/archive.py @@ -542,7 +542,7 @@ Number of files: {0.stats.nfiles}'''.format( raise ChunksIndexError(cid) except Repository.ObjectNotFound as e: # object not in repo - strange, but we wanted to delete it anyway. - if not forced: + if forced == 0: raise error = True @@ -564,14 +564,14 @@ Number of files: {0.stats.nfiles}'''.format( except (TypeError, ValueError): # if items metadata spans multiple chunks and one chunk got dropped somehow, # it could be that unpacker yields bad types - if not forced: + if forced == 0: raise error = True if progress: pi.finish() except (msgpack.UnpackException, Repository.ObjectNotFound): # items metadata corrupted - if not forced: + if forced == 0: raise error = True # in forced delete mode, we try hard to delete at least the manifest entry, diff --git a/borg/archiver.py b/borg/archiver.py index 5abf923d9..83be6ba02 100644 --- a/borg/archiver.py +++ b/borg/archiver.py @@ -503,9 +503,23 @@ class Archiver: def do_delete(self, args, repository): """Delete an existing repository or archive""" if args.location.archive: + archive_name = args.location.archive 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: - archive = Archive(repository, key, manifest, args.location.archive, cache=cache) + archive = Archive(repository, key, manifest, archive_name, cache=cache) stats = Statistics() archive.delete(stats, progress=args.progress, forced=args.forced) manifest.write() @@ -1554,8 +1568,9 @@ class Archiver: action='store_true', default=False, help='delete only the local cache for the given repository') 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', default=False, help='work slower, but using less space') diff --git a/borg/testsuite/archiver.py b/borg/testsuite/archiver.py index c5e4f2b81..2100808d8 100644 --- a/borg/testsuite/archiver.py +++ b/borg/testsuite/archiver.py @@ -914,6 +914,20 @@ class ArchiverTestCase(ArchiverTestCaseBase): # Make sure the repo is gone 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): self.cmd('init', self.repository_location) self.create_src_archive('test')