Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion src/borg/archiver/delete_cmd.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ def do_delete(self, args, repository):
logger_list = logging.getLogger("borg.output.list")
for i, archive_info in enumerate(archive_infos, 1):
name, id, hex_id = archive_info.name, archive_info.id, bin_to_hex(archive_info.id)
# format early before deletion of the archive
archive_formatted = format_archive(archive_info)
try:
# this does NOT use Archive.delete, so this code hopefully even works in cases a corrupt archive
# would make the code in class Archive crash, so the user can at least get rid of such archives.
Expand All @@ -47,7 +49,7 @@ def do_delete(self, args, repository):
deleted = True
if self.output_list:
msg = "Would delete: {} ({}/{})" if dry_run else "Deleted archive: {} ({}/{})"
logger_list.info(msg.format(format_archive(archive_info), i, count))
logger_list.info(msg.format(archive_formatted, i, count))
if dry_run:
logger.info("Finished dry-run.")
elif deleted:
Expand Down
17 changes: 10 additions & 7 deletions src/borg/archiver/prune_cmd.py
Original file line number Diff line number Diff line change
Expand Up @@ -179,29 +179,32 @@ def do_prune(self, args, repository, manifest):
archives_deleted = 0
uncommitted_deletes = 0
pi = ProgressIndicatorPercent(total=len(to_delete), msg="Pruning archives %3.0f%%", msgid="prune")
for archive in archives:
for archive_info in archives:
if sig_int and sig_int.action_done():
break
if archive in to_delete:
# format_item may internally load the archive from the repository,
# so we must call it before deleting the archive.
archive_formatted = formatter.format_item(archive_info, jsonline=False)
if archive_info in to_delete:
pi.show()
if args.dry_run:
log_message = "Would prune:"
else:
archives_deleted += 1
log_message = "Pruning archive (%d/%d):" % (archives_deleted, to_delete_len)
archive = Archive(manifest, archive.id, cache=cache)
archive = Archive(manifest, archive_info.id, cache=cache)
archive.delete()
uncommitted_deletes += 1
else:
log_message = "Keeping archive (rule: {rule} #{num}):".format(
rule=kept_because[archive.id][0], num=kept_because[archive.id][1]
rule=kept_because[archive_info.id][0], num=kept_because[archive_info.id][1]
)
if (
args.output_list
or (args.list_pruned and archive in to_delete)
or (args.list_kept and archive not in to_delete)
or (args.list_pruned and archive_info in to_delete)
or (args.list_kept and archive_info not in to_delete)
):
list_logger.info(f"{log_message:<44} {formatter.format_item(archive, jsonline=False)}")
list_logger.info(f"{log_message:<44} {archive_formatted}")
pi.finish()
if sig_int:
raise Error("Got Ctrl-C / SIGINT.")
Expand Down
16 changes: 16 additions & 0 deletions src/borg/testsuite/archiver/prune_cmd_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -400,3 +400,19 @@ def test_prune_split_no_archives():

assert keep == []
assert kept_because == {}


def test_prune_list_with_metadata_format(archivers, request):
# Regression test for: prune --list with a format string that requires loading
# archive metadata (e.g. {hostname}) must not fail when archives are deleted.
# The bug was that format_item() was called after archive.delete(), causing
# Archive.DoesNotExist when the formatter tried to lazy-load the archive.
archiver = request.getfixturevalue(archivers)
cmd(archiver, "repo-create", RK_ENCRYPTION)
cmd(archiver, "create", "test1", src_dir)
cmd(archiver, "create", "test2", src_dir)
# {hostname} is a "call key" that triggers lazy loading of the archive from the repo.
# With the buggy code this would raise Archive.DoesNotExist for the pruned archive.
output = cmd(archiver, "prune", "--list", "--keep-daily=1", "--format={name} {hostname}{NL}")
assert "test1" in output
assert "test2" in output