mirror of https://github.com/borgbackup/borg.git
Merge pull request #2184 from ThomasWaldmann/zap
borg delete --force --force to delete severely corrupted archives, fixes #1975
This commit is contained in:
commit
9bc825a27a
|
@ -714,7 +714,7 @@ Utilization of max. archive size: {csize_max:.0%}
|
||||||
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
|
||||||
|
|
||||||
|
@ -738,14 +738,14 @@ Utilization of max. archive size: {csize_max:.0%}
|
||||||
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,
|
||||||
|
|
|
@ -844,6 +844,26 @@ class Archiver:
|
||||||
if not archive_names:
|
if not archive_names:
|
||||||
return self.exit_code
|
return self.exit_code
|
||||||
|
|
||||||
|
if args.forced == 2:
|
||||||
|
deleted = False
|
||||||
|
for i, archive_name in enumerate(archive_names, 1):
|
||||||
|
try:
|
||||||
|
del manifest.archives[archive_name]
|
||||||
|
except KeyError:
|
||||||
|
self.exit_code = EXIT_WARNING
|
||||||
|
logger.warning('Archive {} not found ({}/{}).'.format(archive_name, i, len(archive_names)))
|
||||||
|
else:
|
||||||
|
deleted = True
|
||||||
|
logger.info('Deleted {} ({}/{}).'.format(archive_name, i, len(archive_names)))
|
||||||
|
if 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.')
|
||||||
|
else:
|
||||||
|
logger.warning('Aborted.')
|
||||||
|
return self.exit_code
|
||||||
|
|
||||||
stats_logger = logging.getLogger('borg.output.stats')
|
stats_logger = logging.getLogger('borg.output.stats')
|
||||||
if args.stats:
|
if args.stats:
|
||||||
log_multi(DASHES, STATS_HEADER, logger=stats_logger)
|
log_multi(DASHES, STATS_HEADER, logger=stats_logger)
|
||||||
|
@ -861,7 +881,7 @@ class Archiver:
|
||||||
if args.stats:
|
if args.stats:
|
||||||
log_multi(stats.summary.format(label='Deleted data:', stats=stats),
|
log_multi(stats.summary.format(label='Deleted data:', stats=stats),
|
||||||
DASHES, logger=stats_logger)
|
DASHES, logger=stats_logger)
|
||||||
if not args.forced and self.exit_code:
|
if args.forced == 0 and self.exit_code:
|
||||||
break
|
break
|
||||||
if args.stats:
|
if args.stats:
|
||||||
stats_logger.info(str(cache))
|
stats_logger.info(str(cache))
|
||||||
|
@ -2450,8 +2470,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,
|
action='count', default=0,
|
||||||
help='force deletion of corrupted archives')
|
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')
|
||||||
|
|
|
@ -1193,6 +1193,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.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', '--encryption=repokey', self.repository_location)
|
self.cmd('init', '--encryption=repokey', self.repository_location)
|
||||||
self.create_src_archive('test')
|
self.create_src_archive('test')
|
||||||
|
|
Loading…
Reference in New Issue