Skip to content

Commit 231bf26

Browse files
Merge pull request #9200 from ThomasWaldmann/fix-9199-legacyremote-raise-missing
transfer and legacyremote fixes
2 parents a38d87b + 7d4c05d commit 231bf26

File tree

3 files changed

+52
-12
lines changed

3 files changed

+52
-12
lines changed

src/borg/archiver/transfer_cmd.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,16 @@
2020

2121

2222
def transfer_chunks(
23-
upgrader, other_repository, other_manifest, other_chunks, archive, cache, recompress, dry_run, chunker_params=None
23+
upgrader,
24+
other_repository,
25+
other_manifest,
26+
other_chunks,
27+
archive,
28+
cache,
29+
manifest,
30+
recompress,
31+
dry_run,
32+
chunker_params=None,
2433
):
2534
"""
2635
Transfer chunks from another repository to the current repository.
@@ -41,7 +50,7 @@ def transfer_chunks(
4150
file = ChunkIteratorFileWrapper(chunk_iterator)
4251

4352
# Create a chunker with the specified parameters
44-
chunker = get_chunker(*chunker_params, key=archive.key, sparse=False)
53+
chunker = get_chunker(*chunker_params, key=manifest.key, sparse=False)
4554
for chunk in chunker.chunkify(file):
4655
if not dry_run:
4756
chunk_id, data = cached_hash(chunk, archive.key.id_hash)
@@ -226,6 +235,7 @@ def do_transfer(self, args, *, repository, manifest, cache, other_repository=Non
226235
other_chunks,
227236
archive,
228237
cache,
238+
manifest,
229239
args.recompress,
230240
dry_run,
231241
args.chunker_params,

src/borg/legacyremote.py

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -664,11 +664,12 @@ def __len__(self):
664664
def list(self, limit=None, marker=None):
665665
"""actual remoting is done via self.call in the @api decorator"""
666666

667-
def get(self, id, read_data=True):
668-
for resp in self.get_many([id], read_data=read_data):
667+
def get(self, id, read_data=True, raise_missing=True):
668+
for resp in self.get_many([id], read_data=read_data, raise_missing=raise_missing):
669669
return resp
670670

671-
def get_many(self, ids, read_data=True, is_preloaded=False):
671+
def get_many(self, ids, read_data=True, is_preloaded=False, raise_missing=True):
672+
# note: legacy remote protocol does not support raise_missing parameter, so we ignore it here
672673
yield from self.call_many("get", [{"id": id, "read_data": read_data} for id in ids], is_preloaded=is_preloaded)
673674

674675
@api(since=parse_version("1.0.0"))
@@ -747,11 +748,11 @@ def __enter__(self):
747748
def __exit__(self, exc_type, exc_val, exc_tb):
748749
self.close()
749750

750-
def get(self, key, read_data=True):
751-
return next(self.get_many([key], read_data=read_data, cache=False))
751+
def get(self, key, read_data=True, raise_missing=True):
752+
return next(self.get_many([key], read_data=read_data, raise_missing=raise_missing, cache=False))
752753

753-
def get_many(self, keys, read_data=True, cache=True):
754-
for key, data in zip(keys, self.repository.get_many(keys, read_data=read_data)):
754+
def get_many(self, keys, read_data=True, cache=True, raise_missing=True):
755+
for key, data in zip(keys, self.repository.get_many(keys, read_data=read_data, raise_missing=raise_missing)):
755756
yield self.transform(key, data)
756757

757758
def log_instrumentation(self):
@@ -856,10 +857,12 @@ def close(self):
856857
self.cache.clear()
857858
shutil.rmtree(self.basedir)
858859

859-
def get_many(self, keys, read_data=True, cache=True):
860+
def get_many(self, keys, read_data=True, cache=True, raise_missing=True):
860861
# It could use different cache keys depending on read_data and cache full vs. meta-only chunks.
861862
unknown_keys = [key for key in keys if self.prefixed_key(key, complete=read_data) not in self.cache]
862-
repository_iterator = zip(unknown_keys, self.repository.get_many(unknown_keys, read_data=read_data))
863+
repository_iterator = zip(
864+
unknown_keys, self.repository.get_many(unknown_keys, read_data=read_data, raise_missing=raise_missing)
865+
)
863866
for key in keys:
864867
pkey = self.prefixed_key(key, complete=read_data)
865868
if pkey in self.cache:
@@ -877,7 +880,7 @@ def get_many(self, keys, read_data=True, cache=True):
877880
else:
878881
# slow path: eviction during this get_many removed this key from the cache
879882
t0 = time.perf_counter()
880-
data = self.repository.get(key, read_data=read_data)
883+
data = self.repository.get(key, read_data=read_data, raise_missing=raise_missing)
881884
self.slow_lat += time.perf_counter() - t0
882885
transformed = self.add_entry(key, data, cache, complete=read_data)
883886
self.slow_misses += 1

src/borg/testsuite/archiver/transfer_cmd_test.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -473,6 +473,33 @@ def test_transfer_rechunk(archivers, request, monkeypatch):
473473
assert dest_hash == source_file_hashes[item.path], f"Content hash mismatch for {item.path}"
474474

475475

476+
def test_transfer_rechunk_dry_run(archivers, request, monkeypatch):
477+
"""Ensure --dry-run works together with --chunker-params (re-chunking path).
478+
479+
This specifically guards against regressions like AttributeError when archive is None
480+
during dry-run (see issue #9199).
481+
"""
482+
archiver = request.getfixturevalue(archivers)
483+
484+
BLKSIZE = 512
485+
source_chunker_params = "buzhash,19,23,21,4095" # default-ish buzhash parameters
486+
dest_chunker_params = f"fixed,{BLKSIZE}" # simple deterministic chunking
487+
488+
# Prepare source repo and create one archive
489+
with setup_repos(archiver, monkeypatch) as other_repo1:
490+
contents = random.randbytes(8 * BLKSIZE)
491+
create_regular_file(archiver.input_path, "file.bin", contents=contents)
492+
cmd(archiver, "create", f"--chunker-params={source_chunker_params}", "arch", "input")
493+
494+
# Now we are in the destination repo (setup_repos switched us on context exit).
495+
# Run transfer in dry-run mode with re-chunking. This must not crash.
496+
cmd(archiver, "transfer", other_repo1, "--dry-run", f"--chunker-params={dest_chunker_params}")
497+
498+
# Dry-run must not have created archives in the destination repo.
499+
listing = cmd(archiver, "repo-list")
500+
assert "arch" not in listing
501+
502+
476503
def test_issue_9022(archivers, request, monkeypatch):
477504
"""
478505
Regression test for borgbackup/borg#9022: After "borg transfer --from-borg1",

0 commit comments

Comments
 (0)