Commit graph

10186 commits

Author SHA1 Message Date
TW
350ec8bc36
Merge pull request #9744 from mr-raj12/pack-files-step6-range-load
Some checks are pending
Lint / lint (push) Waiting to run
CI / lint (push) Waiting to run
CI / security (push) Waiting to run
CI / asan_ubsan (push) Blocked by required conditions
CI / native_tests (push) Blocked by required conditions
CI / vm_tests (NetBSD, false, netbsd, 10.1) (push) Blocked by required conditions
CI / vm_tests (OmniOS, false, omnios, r151056) (push) Blocked by required conditions
CI / vm_tests (OpenBSD, false, openbsd, 7.8) (push) Blocked by required conditions
CI / vm_tests (borg-freebsd-14-x86_64-gh, FreeBSD, true, freebsd, 14.3) (push) Blocked by required conditions
CI / windows_tests (push) Blocked by required conditions
CI / sha256 pack-id (informational) (push) Blocked by required conditions
CodeQL / Analyze (push) Waiting to run
repository: ChunkIndex-based pack routing with range-load support
2026-06-14 16:47:10 +02:00
TW
46d5c96cb4
Merge pull request #9773 from ThomasWaldmann/py310-modernize
use Python 3.10 features: match statements and PEP 604 unions
2026-06-14 15:36:29 +02:00
Mrityunjay Raj
ea4be4d2c4 repository: make failing-store test helper fail only pack writes
Lets one store both fail the pack write and persist the index, matching production.
2026-06-14 18:36:13 +05:30
Thomas Waldmann
b46bfdab18 use structural pattern matching (match/case, Python 3.10)
Convert if/elif chains that dispatch on a single value to match
statements: repository.borg_permissions (permission preset),
CompressionSpec.__init__/compressor/__str__ (compression name),
calculate_relative_offset (relative ts unit) and the cockpit widget
file-status counter. Membership checks like 'in ("none", "lz4")'
become alternative patterns ('none' | 'lz4').

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 14:04:25 +02:00
Thomas Waldmann
f25c580ec4 use PEP 604 union types (X | None) instead of typing.Optional
Replace the remaining typing.Optional[...] annotations with the
X | None syntax (PEP 604, Python 3.10) and drop the now-unused
Optional/List imports; List[str] -> list[str] in cockpit/runner.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 14:02:33 +02:00
TW
70226f0a5a
Merge pull request #9772 from ThomasWaldmann/py311-modernize
Use some Python 3.11 features (StrEnum, datetime.UTC, contextlib.chdir)
2026-06-14 13:55:27 +02:00
Thomas Waldmann
fe1f0c7d76 use contextlib.chdir (Python 3.11) in tests
Replace the hand-rolled changedir() context manager with stdlib
contextlib.chdir (kept under the changedir name for importers), and
use it in the archiver fixture so the cwd is restored even if the
test body raises.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 12:20:40 +02:00
Thomas Waldmann
0ebfb55df0 use Python 3.11 features: StrEnum and datetime.UTC
Manifest.Operation now derives from enum.StrEnum, so its members are
real str instances; drop the .value indirection in the feature-flag
lookups.

Replace timezone.utc with the datetime.UTC alias (3.11) across the
non-test modules and drop the now-unused timezone imports.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 12:20:29 +02:00
Mrityunjay Raj
f917c90256 repository: roll back chunk index on failed pack store
flush() drops the entries add() pre-marked when store.store() raises, so a never-stored chunk is not left indexed for dedup. adds rollback tests, bumps large-meta test to 5KB, drops security wording from the read_data=False clamp comments.
2026-06-14 15:40:43 +05:30
TW
ff3133edc6
Merge pull request #9771 from ThomasWaldmann/fix-spoofed-archive-flaky
Some checks are pending
Lint / lint (push) Waiting to run
CI / lint (push) Waiting to run
CI / security (push) Waiting to run
CI / asan_ubsan (push) Blocked by required conditions
CI / native_tests (push) Blocked by required conditions
CI / vm_tests (NetBSD, false, netbsd, 10.1) (push) Blocked by required conditions
CI / vm_tests (OmniOS, false, omnios, r151056) (push) Blocked by required conditions
CI / vm_tests (OpenBSD, false, openbsd, 7.8) (push) Blocked by required conditions
CI / vm_tests (borg-freebsd-14-x86_64-gh, FreeBSD, true, freebsd, 14.3) (push) Blocked by required conditions
CI / windows_tests (push) Blocked by required conditions
CodeQL / Analyze (push) Waiting to run
testsuite: fix flaky byte-corruption in check_cmd tests
2026-06-14 00:41:40 +02:00
TW
5e2da157be
Merge pull request #9769 from ThomasWaldmann/docs-key-subcommands
docs: add docs for "key add", "key list" and "key remove"
2026-06-13 23:47:58 +02:00
Thomas Waldmann
27401aa02c testsuite: fix flaky byte-corruption in check_cmd tests
Several check_cmd tests corrupted a repo object by overwriting a byte at
a fixed position with a fixed value, e.g.:

    manifest[:250] + b"x" + manifest[251:]

Manifests/chunks are stored as AEAD-encrypted repo objects, so their
bytes are ~random. When the target byte already happened to hold the
overwrite value (~1/256), the "corruption" was a no-op: the object
stayed valid, "check" returned 0 instead of 1, and the test failed
intermittently (observed in test_spoofed_archive).

Introduce a corrupt(data, position) helper that flips the byte (XOR
0xFF), so the result is guaranteed to differ, and use it in all the
byte-overwrite corruption sites: test_corrupted_manifest,
test_spoofed_manifest, test_manifest_rebuild_corrupted_chunk,
test_spoofed_archive, test_verify_data and test_corrupted_file_chunk.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 23:14:11 +02:00
TW
b2d54c68d4
Merge pull request #9767 from ThomasWaldmann/unify-keyfile-repokey
key: unify keyfile/repokey classes, locate key independent of type byte (#9743)
2026-06-13 22:20:22 +02:00
TW
dab428d5e4
Merge pull request #9770 from ThomasWaldmann/canary-py314
canary: downgrade 3.15-dev to 3.14
2026-06-13 22:19:12 +02:00
Thomas Waldmann
e0a040a30b canary: downgrade 3.15-dev to 3.14
Python 3.15-dev is not compatible with some of our requirements yet,
so use 3.14 (py314-mfusepy) for the canary matrix entry.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 22:17:46 +02:00
Thomas Waldmann
d5cd72b179 docs: add docs for "key add", "key list" and "key remove"
The multi-key feature (#9743) added the "borg key add", "borg key list"
and "borg key remove" subcommands but never wired up their docs:

- scripts/make.py: map key_add/key_list/key_remove to the "key" usage
  group, so build_man can locate their examples (it previously aborted
  with FileNotFoundError: docs/usage/key_add.rst).
- docs/usage/key.rst: include the three new generated snippets so they
  show up on the HTML key page.
- regenerate the affected docs: new key_{add,list,remove}.rst.inc usage
  snippets, new borg-key-{add,list,remove}.1 man pages, and borg-key.1
  (SYNOPSIS + SEE ALSO now reference the new subcommands).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 21:37:21 +02:00
Thomas Waldmann
8f4231d21b key: allow --key-location for authenticated* modes
The authenticated and authenticated-blake3 modes do not encrypt data, but
they still have a real key (id/auth key material) stored as a key blob.
That blob can live as a keyfile or as a repokey just like the encrypted
modes, so make it configurable instead of always forcing repokey storage.

- AuthenticatedKeyBase: set LOCATION_CONFIGURABLE = True so --key-location
  (at repo-create) and "borg key change-location" apply.
- key change-location: only copy sessionid/cipher when present (those are
  AEAD-only; authenticated keys do not have them).
- repo-info: report the key storage location for authenticated keys too,
  and handle the authenticated-blake3 variant (was only "authenticated").
- repo-create help: stop claiming authenticated* has no keyfile/repokey
  storage; only "none" truly has no key.
- add change-location round-trip tests for authenticated mode.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 20:57:26 +02:00
Mrityunjay Raj
e6010b5793 repository: address chunk-index review feedback
PackWriter requires a real/explicit index (no silent private one); PackLocationUnknown is now a loud ErrorWithTraceback and get() raises it unconditionally; close() persists via is_chunk_index_loaded/self.chunks; delete_chunkindex_cache calls invalidate_chunk_index() directly; drop per-access fragment consolidation (compact reclaims instead); capture BORG_TESTONLY_SHA256_PACK_ID at import; remove dead _current_offset; update tests.
2026-06-13 14:47:07 +05:30
Thomas Waldmann
d2bc45f56d
key: unify keyfile/repokey classes, locate key independent of type byte (#9743)
Borg used to read the manifest's key-type byte and then look for the key in
exactly one place (keyfile or repokey) depending on the key class that byte
selected. As a result every crypto suite was duplicated into a keyfile class
and a repokey class that differed only in TYPE, NAME, ARG_NAME and STORAGE.

Now key *location* is independent of the type byte: detection tries keyfiles
first and repokeys afterwards until a passphrase unlocks a key. The type byte
still selects the crypto suite (id hash, MAC, cipher) to instantiate. Where a
key is stored (keyfile vs repokey) is therefore a per-key property
(self.storage), not a separate class, so a repository may even hold a mix of
keyfile- and repo-stored borg keys.

With storage decoupled from class identity, the keyfile/repokey class pairs
collapse into one class per crypto suite:
- modern AEAD: AESOCBKey, CHPOKey, Blake3AESOCBKey, Blake3CHPOKey
- legacy borg 1.x (read-only): AESCTRKey, Blake2AESCTRKey
There is now exactly one type byte per modern crypto suite (the old separate
repokey type bytes 0x11/0x21/0x31/0x41 were removed; borg2 is beta and only
needs to read repos it created). identify_key() matches on TYPES_ACCEPTABLE.

CLI: --encryption selects only the crypto suite (aes-ocb, chacha20-poly1305,
blake3-aes-ocb, blake3-chacha20-poly1305, authenticated*, none); the storage
location is chosen with the new --key-location=repokey|keyfile (default
repokey). The old combined modes (repokey-aes-ocb etc.) were removed.
borg key import also gained --key-location. borg key change-location no longer
swaps key classes or rewrites the manifest; it just re-saves the unlocked key
at the new location.

Keyfile removal (key remove, change-location) now overwrites the keyfile with
random data via secure_erase() before unlinking, consistent with save().

borg 1.x legacy read compatibility is preserved (the legacy class merge is a
behavior-preserving rename; the legacy type bytes incl. PASSPHRASE stay in
TYPES_ACCEPTABLE).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 23:48:45 +02:00
TW
e0c7a68a3e
Merge pull request #9762 from ThomasWaldmann/multi-keyblob-9743
Some checks are pending
Lint / lint (push) Waiting to run
CI / lint (push) Waiting to run
CI / security (push) Waiting to run
CI / asan_ubsan (push) Blocked by required conditions
CI / native_tests (push) Blocked by required conditions
CI / vm_tests (NetBSD, false, netbsd, 10.1) (push) Blocked by required conditions
CI / vm_tests (OmniOS, false, omnios, r151056) (push) Blocked by required conditions
CI / vm_tests (OpenBSD, false, openbsd, 7.8) (push) Blocked by required conditions
CI / vm_tests (borg-freebsd-14-x86_64-gh, FreeBSD, true, freebsd, 14.3) (push) Blocked by required conditions
CI / windows_tests (push) Blocked by required conditions
CodeQL / Analyze (push) Waiting to run
key: multiple keys per repository (#9743)
2026-06-12 23:16:06 +02:00
Thomas Waldmann
af0904f5f0
key: support multiple borg keys per repository, fixes #9743
A repository can now have multiple borg keys protected by individual
(usually different) passphrases, each borg key is stored separately.
Any of the keys can be used to decrypt the repository, all contain
the same secret key material.

Keys now have a LABEL (first key always has "admin" label) for easier
identification. The "admin" key is protected and can't be removed.

Repository now stores N borg keys under keys/ instead of one.
LegacyRepository stays single-key.

To find the right key, borg tries a passphrase against every borg key.

CLI:
- borg key list
- borg key add --label
- borg key remove --label|--key|--passphrase
- borg key export --label|--key

Reading borg 1.x keyfiles and repokey (borg transfer --from-borg1)
keeps working: the legacy key classes share FlexiKey, so enumeration
falls back to load_key() and headerless 1.x repokey blobs still parse.

The keyblob on-disk format is unchanged; multiplicity lives only in the
storage/enumeration layer.

tests: adapt test_repo_key_detect_does_not_raise_integrity_error

Updated to the multiple-borg-keys API: store_key.call_args for the saved blob and
load_keys.return_value to feed it back to detect().

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 20:38:42 +02:00
TW
08b1a67928
Merge pull request #9763 from ThomasWaldmann/fix-stale-fuse-mounts
Some checks are pending
Lint / lint (push) Waiting to run
CI / lint (push) Waiting to run
CI / security (push) Waiting to run
CI / asan_ubsan (push) Blocked by required conditions
CI / native_tests (push) Blocked by required conditions
CI / vm_tests (NetBSD, false, netbsd, 10.1) (push) Blocked by required conditions
CI / vm_tests (OmniOS, false, omnios, r151056) (push) Blocked by required conditions
CI / vm_tests (OpenBSD, false, openbsd, 7.8) (push) Blocked by required conditions
CI / vm_tests (borg-freebsd-14-x86_64-gh, FreeBSD, true, freebsd, 14.3) (push) Blocked by required conditions
CI / windows_tests (push) Blocked by required conditions
CodeQL / Analyze (push) Waiting to run
testsuite: sweep stale borg FUSE mounts left by aborted runs
2026-06-12 16:03:53 +02:00
Thomas Waldmann
5541121f4e testsuite: sweep stale borg FUSE mounts left by aborted runs
When a test run is aborted (Ctrl-C, a timeout, or a crashed xdist worker),
the fuse_mount context manager's `finally: umount` does not run, leaving a
live FUSE mount under the pytest temp dir. A later run then fails to rm_rf
the old temp tree (Resource busy / Device not configured / Read-only file
system), which surfaces as noisy pytest warnings.

Add pytest_sessionstart/sessionfinish hooks (run once on the xdist
controller) that walk the pytest temp tree for leftover "mountpoint"
directories and unmount them via borg's own cross-platform umount helper,
making runs self-healing across previously aborted sessions.

Discovery uses only the stdlib (os.walk + os.path.ismount, with an
os.listdir fallback for dead/stale mounts) and the mount is named by the
well-known on-disk layout, so there is no OS-specific mount-table parsing or
force-unmount handling.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 13:55:02 +02:00
Mrityunjay Raj
445f2605ef repository: make the chunk index single-owned by the repository
PackWriter and the cache now share repository.chunks instead of keeping their own copies; set_chunk_index is gone (use the writable .chunks property). The cache consolidates fragmented cache/chunks.* files so they no longer grow by one per backup until the next check or compact.
2026-06-12 16:22:07 +05:30
Mrityunjay Raj
8b5c2d857e repository: invalidate in-memory chunk index when its cache is deleted
delete_chunkindex_cache() now also drops the repository's in-memory index, so
close() cannot persist a stale copy and callers no longer clear it by hand.
get() now raises a distinct PackLocationUnknown when a chunk is indexed but not
yet flushed, rather than a plain ObjectNotFound.
2026-06-12 12:53:20 +05:30
Mrityunjay Raj
cae28f2dd1 check: drop stale in-memory chunk index after --repair clears the cache
finish() deletes cache/chunks.* and now also invalidates the live index, so close() does not write it back. The next repository access rebuilds it from actual repository contents.
2026-06-12 12:15:27 +05:30
Mrityunjay Raj
abb32abf2d repository: persist chunk index to repo cache on close()
close() now writes the in-memory index back so the next session can resolve chunk locations instead of failing with ObjectNotFound. Empty incremental writes are skipped so a deleted index is not recreated as a stale, empty one.
2026-06-12 12:15:27 +05:30
Mrityunjay Raj
0161a54a71 ci/tests: exempt BORG_TESTONLY_SHA256_PACK_ID from clean_env; add concurrency to sha256 job
env var was wiped before every test; sha256 job now gets its own concurrency group so it is not cancelled mid-run
2026-06-12 12:15:27 +05:30
Mrityunjay Raj
066004800c repository: simplify chunks property; trigger lazy build before put()
Remove overlay loop: put() accesses self.chunks first so PackWriter.chunks is updated before any write.
2026-06-12 12:15:27 +05:30
Mrityunjay Raj
844daf9324 repository: add set_chunk_index() and lazy chunks property; route get() through ChunkIndex
PackWriter now always owns a ChunkIndex; the N=1 fallback in get() is removed.
2026-06-12 12:15:27 +05:30
Mrityunjay Raj
05ce183f71 repository: add BORG_TESTONLY_SHA256_PACK_ID to force sha256 pack_ids at N=1
Adds tox env and informational CI job (continue-on-error) to track progress toward full sha256 pack_id adoption, refs #8572
2026-06-12 12:15:27 +05:30
Mrityunjay Raj
67b11f7826 repository: PackWriter now manages ChunkIndex updates internally
On add(), marks chunk with UNKNOWN_BYTES32; on flush(), replaces with real
pack_id. put(), flush(), and set_chunk_index() simplified accordingly.
2026-06-12 12:15:27 +05:30
Mrityunjay Raj
1df2065f85 repository: remove N=1 fallback from get(), update _chunks eagerly in put()
get() raises ObjectNotFound when entry is missing or UNKNOWN_BYTES32; put()
marks the id in _chunks immediately so the index is live after each write.
2026-06-12 12:15:27 +05:30
Mrityunjay Raj
54adb9b7f6 repository: resolve pack location from ChunkIndex in get(), flush handles update_pack_info
Remove obj_offset/obj_size params from get(); always initialize _chunks to an
empty ChunkIndex so callers never need to guard for None.
2026-06-12 12:15:27 +05:30
Mrityunjay Raj
68491c4409 repository: add set_chunk_index() for ChunkIndex-based pack routing, refs #8572
Replace _pack_info (session-scoped dict) with a borrowed ChunkIndex reference.
Cache passes its index via set_chunk_index(); get() routes correctly for all sessions.
2026-06-12 12:15:27 +05:30
Mrityunjay Raj
2553a705cc repository: add obj_offset/obj_size range-load params to Store.load calls
retry_size min() guards against corrupted meta_size, no-op for healthy objects.
2026-06-12 12:15:26 +05:30
TW
c710edf0df
Merge pull request #9754 from ThomasWaldmann/simplify-location-parsing
Some checks are pending
Lint / lint (push) Waiting to run
CI / lint (push) Waiting to run
CI / security (push) Waiting to run
CI / asan_ubsan (push) Blocked by required conditions
CI / native_tests (push) Blocked by required conditions
CI / vm_tests (NetBSD, false, netbsd, 10.1) (push) Blocked by required conditions
CI / vm_tests (OmniOS, false, omnios, r151056) (push) Blocked by required conditions
CI / vm_tests (OpenBSD, false, openbsd, 7.8) (push) Blocked by required conditions
CI / vm_tests (borg-freebsd-14-x86_64-gh, FreeBSD, true, freebsd, 14.3) (push) Blocked by required conditions
CI / windows_tests (push) Blocked by required conditions
CodeQL / Analyze (push) Waiting to run
Simplify location parsing, remove socket: remainders
2026-06-11 10:48:23 +02:00
TW
23d5152840
Merge pull request #9756 from ThomasWaldmann/exclude-dataless-master
create: add --exclude-dataless to skip cloud files not materialized locally
2026-06-11 09:39:23 +02:00
Thomas Waldmann
f484741fdd
create: add --exclude-dataless to skip cloud files not materialized locally
Ports #9755 (1.4-maint) to master.

macOS flags files whose content lives in cloud storage (e.g. iCloud Drive)
and is not present locally with SF_DATALESS. Reading such a file triggers
downloading its content.

This adds --exclude-dataless to borg create: the flags are checked right
after stat() and before any open(), so excluded files/dirs never get
materialized. Skipped paths are reported with the usual 'x' status,
analogous to --exclude-nodump.

Notes:
- stat.SF_DATALESS only exists from Python 3.13 on, so there is a fallback
  to the value from macOS' sys/stat.h (0x40000000).
- The check covers directories too (they can also be dataless), and we skip
  before opening/recursing into them.
- Test fakes get_flags via monkeypatch since SF_DATALESS cannot be set from
  userspace.
- Fish shell completion updated; bash/zsh completions don't exist in master.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-11 07:36:53 +02:00
Thomas Waldmann
5eab183183
remove leftover socket: protocol code
The unix-socket transport (socket:// repositories, the --socket option and
"borg serve" over a socket) was never part of a stable borg 2 release and the
old RPC protocol it relied on is gone, so the remaining code was dead:

- legacy remote: drop the unreachable proto == "socket" connection branch and
  the now-unused self.sock handling, "import socket" and get_socket_filename
  import (LegacyRemoteRepository is only built for proto == "ssh")
- helpers: remove get_socket_filename() and its export
- parseformat: drop "socket" from local_path_re - socket:// is now treated like
  any other unknown scheme (a local path) rather than being special-cased
- tests: drop test_socket and the self.sock check in the legacy reopen helper
- docs: drop the stale --socket entry from the manually maintained
  common-options.rst.inc (the auto-generated usage/man docs are left untouched
  here and will be rebuilt in a separate commit)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 18:51:55 +02:00
TW
86fd77fd33
Merge pull request #9753 from ThomasWaldmann/fix-9748
Some checks are pending
Lint / lint (push) Waiting to run
CI / lint (push) Waiting to run
CI / security (push) Waiting to run
CI / asan_ubsan (push) Blocked by required conditions
CI / native_tests (push) Blocked by required conditions
CI / vm_tests (NetBSD, false, netbsd, 10.1) (push) Blocked by required conditions
CI / vm_tests (OmniOS, false, omnios, r151056) (push) Blocked by required conditions
CI / vm_tests (OpenBSD, false, openbsd, 7.8) (push) Blocked by required conditions
CI / vm_tests (borg-freebsd-14-x86_64-gh, FreeBSD, true, freebsd, 14.3) (push) Blocked by required conditions
CI / windows_tests (push) Blocked by required conditions
CodeQL / Analyze (push) Waiting to run
compact: invalidate cached chunk indexes before deleting objects, #9748
2026-06-10 18:37:06 +02:00
Thomas Waldmann
aa9f810453
parseformat: simplify Location parsing/validation, #9678
For sftp/http(s)/s3/b2/rclone repositories, borg only detects the scheme now
and hands the raw URL to borgstore, which parses and validates it - removing
the duplicate parsing borg used to do. Precise field extraction (user/host/
port/path) is kept only for the protocols borg itself reads: file, rest and
legacy ssh.

- drop http_re, s3_re, rclone_re and the sftp arm of the old ssh_or_sftp_re
- add a single scheme-detection pass-through against BORGSTORE_SCHEMES; reject
  unknown schemes (e.g. socket://) as before
- canonical_path() returns the processed URL for the delegated protocols, with
  embedded credentials stripped so secrets never reach the security state file
  or logs
- source local_path_re's scheme exclusions from BORGSTORE_SCHEMES
- create: use proto == "file" instead of "not location.host" for the local
  repo-dir inode skip

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 18:17:40 +02:00
Thomas Waldmann
0b103291b8
compact: invalidate cached chunk indexes before deleting objects, #9748
If `borg compact` was interrupted after deleting repository objects but
before writing the updated chunk index, the still-existing cache/chunks.*
kept claiming the deleted objects were present. A later `borg create` would
trust that stale index, skip re-uploading the affected chunks and silently
create an archive with dangling object references that extracts to zero bytes.

Invalidate all cached chunk indexes via delete_chunkindex_cache() before the
first object is deleted, so an interruption is conservative: the next client
rebuilds the index from actual repository contents and re-uploads any deleted
data. The post-deletion save_chunk_index() still writes a fresh, valid index.

Add a regression test covering both compact paths (default and --stats) that
interrupts compaction right before save_chunk_index() and verifies no cached
chunk index survives and a later create+extract reproduces the original bytes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 15:45:57 +02:00
TW
8509b3b7fa
Merge pull request #9751 from ThomasWaldmann/fix-9749
Some checks are pending
Lint / lint (push) Waiting to run
CI / lint (push) Waiting to run
CI / security (push) Waiting to run
CI / asan_ubsan (push) Blocked by required conditions
CI / native_tests (push) Blocked by required conditions
CI / vm_tests (NetBSD, false, netbsd, 10.1) (push) Blocked by required conditions
CI / vm_tests (OmniOS, false, omnios, r151056) (push) Blocked by required conditions
CI / vm_tests (OpenBSD, false, openbsd, 7.8) (push) Blocked by required conditions
CI / vm_tests (borg-freebsd-14-x86_64-gh, FreeBSD, true, freebsd, 14.3) (push) Blocked by required conditions
CI / windows_tests (push) Blocked by required conditions
CodeQL / Analyze (push) Waiting to run
files cache: don't empty cache on a no-change backup, #9749
2026-06-10 14:48:05 +02:00
TW
d2cdeaff57
Merge pull request #9750 from ThomasWaldmann/remove-xxh64
remove xxhash / xxh64 requirement, mentions
2026-06-10 14:00:23 +02:00
Thomas Waldmann
c08c2ca461
files cache: don't empty cache on a no-change backup, #9749
FilesCacheMixin initialized _newest_cmtime to 0, but _write_files_cache()
only treats None as "no file was chunked this run" (falling back to a
max_time_ns cutoff that keeps all current entries).

When a backup reuses all files from the cache without chunking anything,
_newest_cmtime stayed at 0, so the race-protection cutoff became the unix
epoch and every current (age == 0) entry was discarded. The next backup
then had to re-read, chunk and hash all files again.

Initialize _newest_cmtime to None to match the documented contract in
_write_files_cache(), and make the comparisons in _build_files_cache()
None-safe.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 12:39:36 +02:00
TW
5a9855dda3
Merge pull request #9747 from ThomasWaldmann/remove-RepositoryNoCache
Remove cache_if_remote and RepositoryNoCache
2026-06-10 11:48:32 +02:00
Thomas Waldmann
31202ea4f1
remove xxhash / xxh64 requirement, mentions 2026-06-10 00:38:18 +02:00
Thomas Waldmann
c35bc660dd
Remove cache_if_remote and RepositoryNoCache
In modern borg these were just a pass-through repository wrapper (there
is no RepositoryCache), with one variant doing inline decryption and
returning (csize, plaintext) tuples. Drop both and make all consumers
use the raw repository directly:

- fuse.py: ItemCache / FuseOperations / FuseBackend now take the raw
  repository + repo_objs and decrypt via repo_objs.parse(ROBJ_DONTCARE),
  matching hlfuse.py. The csize value was discarded at both call sites.
- mount_cmds.py: drop the cache_if_remote wrapper around FuseOperations.
- archive.py (rebuild_archives / check): drop the pass-through wrapper;
  robust_iterator now uses self.repository directly.
- repository.py: delete the RepositoryNoCache class and cache_if_remote.
- repository_test.py: remove TestCacheIfRemote and orphaned imports.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-09 23:36:23 +02:00
TW
ac0a643087
Merge pull request #9745 from ThomasWaldmann/remove-preload
Some checks are pending
Lint / lint (push) Waiting to run
CI / lint (push) Waiting to run
CI / security (push) Waiting to run
CI / asan_ubsan (push) Blocked by required conditions
CI / native_tests (push) Blocked by required conditions
CI / vm_tests (NetBSD, false, netbsd, 10.1) (push) Blocked by required conditions
CI / vm_tests (OmniOS, false, omnios, r151056) (push) Blocked by required conditions
CI / vm_tests (OpenBSD, false, openbsd, 7.8) (push) Blocked by required conditions
CI / vm_tests (borg-freebsd-14-x86_64-gh, FreeBSD, true, freebsd, 14.3) (push) Blocked by required conditions
CI / windows_tests (push) Blocked by required conditions
CodeQL / Analyze (push) Waiting to run
repository: remove chunk preloading
2026-06-09 22:51:52 +02:00