borg/src/borg/archiver/delete_cmd.py

163 lines
6.9 KiB
Python

import argparse
import logging
from ._common import with_repository, Highlander
from ..archive import Archive, Statistics
from ..cache import Cache
from ..constants import * # NOQA
from ..helpers import log_multi, format_archive, sig_int
from ..manifest import Manifest
from ..logger import create_logger
logger = create_logger()
class DeleteMixIn:
@with_repository(exclusive=True, manifest=False)
def do_delete(self, args, repository):
"""Delete archives"""
self.output_list = args.output_list
dry_run = args.dry_run
manifest = Manifest.load(repository, (Manifest.Operation.DELETE,))
archive_names = tuple(x.name for x in manifest.archives.list_considering(args))
if not archive_names:
return self.exit_code
if args.match_archives is None and args.first == 0 and args.last == 0:
self.print_error(
"Aborting: if you really want to delete all archives, please use -a 'sh:*' "
"or just delete the whole repository (might be much faster)."
)
return EXIT_ERROR
if args.forced == 2:
deleted = False
logger_list = logging.getLogger("borg.output.list")
for i, archive_name in enumerate(archive_names, 1):
try:
current_archive = manifest.archives.pop(archive_name)
except KeyError:
self.exit_code = EXIT_WARNING
logger.warning(f"Archive {archive_name} not found ({i}/{len(archive_names)}).")
else:
deleted = True
if self.output_list:
msg = "Would delete: {} ({}/{})" if dry_run else "Deleted archive: {} ({}/{})"
logger_list.info(msg.format(format_archive(current_archive), i, len(archive_names)))
if dry_run:
logger.info("Finished dry-run.")
elif deleted:
manifest.write()
# note: might crash in compact() after committing the repo
repository.commit(compact=False)
logger.warning('Done. Run "borg check --repair" to clean up the mess.')
else:
logger.warning("Aborted.")
return self.exit_code
stats = Statistics(iec=args.iec)
with Cache(repository, manifest, progress=args.progress, lock_wait=self.lock_wait, iec=args.iec) as cache:
def checkpoint_func():
manifest.write()
repository.commit(compact=False)
cache.commit()
msg_delete = "Would delete archive: {} ({}/{})" if dry_run else "Deleting archive: {} ({}/{})"
msg_not_found = "Archive {} not found ({}/{})."
logger_list = logging.getLogger("borg.output.list")
uncommitted_deletes = 0
for i, archive_name in enumerate(archive_names, 1):
if sig_int and sig_int.action_done():
break
try:
archive_info = manifest.archives[archive_name]
except KeyError:
logger.warning(msg_not_found.format(archive_name, i, len(archive_names)))
else:
if self.output_list:
logger_list.info(msg_delete.format(format_archive(archive_info), i, len(archive_names)))
if not dry_run:
archive = Archive(manifest, archive_name, cache=cache)
archive.delete(stats, progress=args.progress, forced=args.forced)
checkpointed = self.maybe_checkpoint(
checkpoint_func=checkpoint_func, checkpoint_interval=args.checkpoint_interval
)
uncommitted_deletes = 0 if checkpointed else (uncommitted_deletes + 1)
if sig_int:
# Ctrl-C / SIGINT: do not checkpoint (commit) again, we already have a checkpoint in this case.
raise Error("Got Ctrl-C / SIGINT.")
elif uncommitted_deletes > 0:
checkpoint_func()
if args.stats:
log_multi(str(stats), logger=logging.getLogger("borg.output.stats"))
return self.exit_code
def build_parser_delete(self, subparsers, common_parser, mid_common_parser):
from ._common import process_epilog, define_archive_filters_group
delete_epilog = process_epilog(
"""
This command deletes archives from the repository.
Important: When deleting archives, repository disk space is **not** freed until
you run ``borg compact``.
When in doubt, use ``--dry-run --list`` to see what would be deleted.
When using ``--stats``, you will get some statistics about how much data was
deleted - the "Deleted data" deduplicated size there is most interesting as
that is how much your repository will shrink.
Please note that the "All archives" stats refer to the state after deletion.
You can delete multiple archives by specifying a matching pattern,
using the ``--match-archives PATTERN`` option (for more info on these patterns,
see :ref:`borg_patterns`).
Always first use ``--dry-run --list`` to see what would be deleted.
"""
)
subparser = subparsers.add_parser(
"delete",
parents=[common_parser],
add_help=False,
description=self.do_delete.__doc__,
epilog=delete_epilog,
formatter_class=argparse.RawDescriptionHelpFormatter,
help="delete archive",
)
subparser.set_defaults(func=self.do_delete)
subparser.add_argument("-n", "--dry-run", dest="dry_run", action="store_true", help="do not change repository")
subparser.add_argument(
"--list", dest="output_list", action="store_true", help="output verbose list of archives"
)
subparser.add_argument(
"--consider-checkpoints",
action="store_true",
dest="consider_checkpoints",
help="consider checkpoint archives for deletion (default: not considered).",
)
subparser.add_argument(
"-s", "--stats", dest="stats", action="store_true", help="print statistics for the deleted archive"
)
subparser.add_argument(
"--force",
dest="forced",
action="count",
default=0,
help="force deletion of corrupted archives, " "use ``--force --force`` in case ``--force`` does not work.",
)
subparser.add_argument(
"-c",
"--checkpoint-interval",
metavar="SECONDS",
dest="checkpoint_interval",
type=int,
default=1800,
action=Highlander,
help="write checkpoint every SECONDS seconds (Default: 1800)",
)
define_archive_filters_group(subparser)