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
15 changes: 14 additions & 1 deletion src/borg/repository.py
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,19 @@ def __init__(self, id, repo):
id = bin_to_hex(id)
super().__init__(id, repo)

class PackNotFound(ErrorWithTraceback):
"""Object with key {} is indexed to pack {}, but that whole pack is missing from repository {}."""

exit_mcode = 23

# a missing pack means the index is stale or more than one object was lost.
def __init__(self, id, pack_id, repo):
if isinstance(id, bytes):
id = bin_to_hex(id)
if isinstance(pack_id, bytes):
pack_id = bin_to_hex(pack_id)
super().__init__(id, pack_id, repo)

class ParentPathDoesNotExist(Error):
"""The parent path of the repository directory [{}] does not exist."""

Expand Down Expand Up @@ -836,7 +849,7 @@ def get_many(self, ids, read_data=True, raise_missing=True):
reader = self._cached_pack_reader(entry.pack_id)
except StoreObjectNotFound:
if raise_missing:
raise self.ObjectNotFound(id_, str(self._location)) from None
raise self.PackNotFound(id_, entry.pack_id, str(self._location)) from None
yield None
else:
yield reader.read(entry.obj_offset, entry.obj_size)
Expand Down
19 changes: 19 additions & 0 deletions src/borg/testsuite/repository_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,25 @@ def test_get_many_missing_id_yields_none(repo_fixtures, request):
assert result[2] == repository.get(H(2))


def test_get_many_missing_pack_raises_pack_not_found(repo_fixtures, request):
# get_many raises PackNotFound when a chunk's index entry points to a pack that is not in the
# store; with raise_missing=False it yields None instead.
objects = [(H(i), fchunk(b"payload-%02d" % i, chunk_id=H(i))) for i in range(2)]
with get_repository_from_fixture(repo_fixtures, request) as repository:
repository._pack_writer.max_count = 2 # one pack: {H0, H1}
for chunk_id, chunk in objects:
repository.put(chunk_id, chunk)
repository.flush()
pack_id = repository.chunks[H(0)].pack_id
repository.store_delete("packs/" + bin_to_hex(pack_id)) # delete the pack, keep its index entry

with pytest.raises(Repository.PackNotFound):
list(repository.get_many([H(0)]))
with pytest.raises(Repository.PackNotFound):
list(repository.get_many([H(0), H(1)])) # one id's pack missing in a batch
assert list(repository.get_many([H(0)], raise_missing=False)) == [None] # raise_missing=False -> None


def test_get_many_repeated_ids(repo_fixtures, request):
# A dedup'd item repeats chunk ids, e.g. [A, A, B, C, B]. Each repeat returns the right bytes
# and each pack is loaded once: the cached pack serves the later visits.
Expand Down
Loading