Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Dry-Run Flags to borg extract Command (#8564) #8575

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Binary file added .coverage 2
Binary file not shown.
68 changes: 42 additions & 26 deletions src/borg/archiver/extract_cmd.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ def do_extract(self, args, repository, manifest, archive):
matcher = build_matcher(args.patterns, args.paths)

progress = args.progress
output_list = args.output_list
dry_run = args.dry_run
stdout = args.stdout
sparse = args.sparse
Expand All @@ -56,33 +55,50 @@ def do_extract(self, args, repository, manifest, archive):
else:
pi = None

for item in archive.iter_items(filter):
archive.preload_item_chunks(item, optimize_hardlinks=True)
for item in archive.iter_items():
orig_path = item.path
if strip_components:
item.path = os.sep.join(orig_path.split(os.sep)[strip_components:])
if not args.dry_run:
while dirs and not item.path.startswith(dirs[-1].path):
dir_item = dirs.pop(-1)
try:
archive.extract_item(dir_item, stdout=stdout)
except BackupError as e:
self.print_warning_instance(BackupWarning(remove_surrogates(dir_item.path), e))
if output_list:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did you remove checking output_list?

Copy link
Author

@alighazi288 alighazi288 Dec 25, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ThomasWaldmann I thought it simplified the logging logic. In the updated code, we always log the paths of the items with a prefix indicating whether they are matched or not. Also, since both includes and excludes are being logged, wouldn't it always be True? If it is necessary to conditionally control the logging based on the output_list flag, I can reintroduce that check.

logging.getLogger("borg.output.list").info(remove_surrogates(item.path))
try:
if dry_run:
archive.extract_item(item, dry_run=True, hlm=hlm, pi=pi)
else:
if stat.S_ISDIR(item.mode):
dirs.append(item)
archive.extract_item(item, stdout=stdout, restore_attrs=False)
components = orig_path.split(os.sep)
stripped_path = os.sep.join(components[strip_components:])

if not stripped_path:
continue
item.path = stripped_path

is_matched = matcher.match(orig_path)
log_prefix = "+" if is_matched else "-"
logging.getLogger("borg.output.list").info(f"{log_prefix} {remove_surrogates(item.path)}")

if is_matched:
# Preloading item chunks only if the item will be fetched
archive.preload_item_chunks(item, optimize_hardlinks=True)

if not dry_run:
while dirs and not item.path.startswith(dirs[-1].path):
dir_item = dirs.pop(-1)
try:
archive.extract_item(dir_item, stdout=stdout)
except BackupError as e:
self.print_warning_instance(BackupWarning(remove_surrogates(dir_item.path), e))

try:
if dry_run:
archive.extract_item(item, dry_run=True, hlm=hlm, pi=pi)
else:
archive.extract_item(
item, stdout=stdout, sparse=sparse, hlm=hlm, pi=pi, continue_extraction=continue_extraction
)
except BackupError as e:
self.print_warning_instance(BackupWarning(remove_surrogates(orig_path), e))
if stat.S_ISDIR(item.mode):
dirs.append(item)
archive.extract_item(item, stdout=stdout, restore_attrs=False)
else:
archive.extract_item(
item,
stdout=stdout,
sparse=sparse,
hlm=hlm,
pi=pi,
continue_extraction=continue_extraction,
)
except BackupError as e:
self.print_warning_instance(BackupWarning(remove_surrogates(orig_path), e))

if pi:
pi.finish()

Expand Down
19 changes: 19 additions & 0 deletions src/borg/testsuite/archiver/extract_cmd_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -718,3 +718,22 @@ def test_extract_continue(archivers, request):
assert f.read() == CONTENTS2
with open("input/file3", "rb") as f:
assert f.read() == CONTENTS3


def test_dry_run_extraction_flags(archivers, request):
archiver = request.getfixturevalue(archivers)
cmd(archiver, "repo-create", RK_ENCRYPTION)
create_regular_file(archiver.input_path, "file1", 0)
create_regular_file(archiver.input_path, "file2", 0)
create_regular_file(archiver.input_path, "file3", 0)
cmd(archiver, "create", "test", "input")

output = cmd(archiver, "extract", "--dry-run", "--list", "test", "-e", "input/file3")

expected_output = ["+ input/file1", "+ input/file2", "- input/file3"]
output_lines = output.splitlines()
for expected in expected_output:
assert expected in output_lines, f"Expected line not found: {expected}"
print(output)

assert not os.listdir("output"), "Output directory should be empty after dry-run"