Commit graph

2158 commits

Author SHA1 Message Date
Mrityunjay Raj
22297f4680 fix typos found by codespell (1.4-maint)
- backupped → backed-up (docs/faq.rst)
- abount → about (docs/faq.rst)
- unflexible → inflexible (docs/faq.rst)
- shrinked → shrunk (docs/internals/data-structures.rst)
- backupped → backed up (src/borg/archive.py)
- splitted → expected (src/borg/testsuite/xattr.py)

Closes #9295
2026-02-13 00:40:54 +05:30
MultisampledNight
261705c4c1
test(compress): roundtrip through UI parsing 2026-02-09 23:42:46 +01:00
MultisampledNight
9b84a2c235
compress: expose Padmé size obfuscation via UI
Fixes #9286.
Tested manually via:

 = export BORG_REPO=repo
 = alias b='python -m borg'
 = echo meow > file
 = b create ::test ./file -C obfuscate,250,none
 = b info
Archive name: ::test
Archive fingerprint: 2c67ba6c04faa6d911b5dd02b99593ea2cf6ebe7c38e2e6fca90afad47a7095e
[...]
Command line: /home/user/c/d0/software/backup/borg/src/borg/__main__.py create ::test ./file -C obfuscate,250,none
[...]
Number of files: 1
Original size: 40 B
 = rm file
 = b list ::test
-rw-r--r-- user   users         5 Mon, 2026-02-09 22:41:54 +0100 file
 = b extract ::test file
 = cat file
meow
 = # Yup, all good. Let's go for something nontrivial to make sure it's stripped accordingly.
 = dd if=/dev/urandom of=file bs=1M count=4
 = b create ::test ./file -C obfuscate,250,zstd
 = mv file original
 = b info
Archive name: ::test
Archive fingerprint: 2c67ba6c04faa6d911b5dd02b99593ea2cf6ebe7c38e2e6fca90afad47a7095e
[...]
Command line: /home/user/c/d0/software/backup/borg/src/borg/__main__.py create ::test ./file -C obfuscate,250,none
Working Directory: /home/user/l/bug/borg
Number of files: 1
Original size: 40 B

Archive name: ::test
Archive fingerprint: 01014345dd1187f014f19ac59a2313a4e6d644532b287d806fe87882ca47ea0d
[...]
Command line: /home/user/c/d0/software/backup/borg/src/borg/__main__.py create ::test ./file -C obfuscate,250,zstd
Working Directory: /home/user/l/bug/borg
Number of files: 1
Original size: 4.19 MB
 = b extract aid:01014345dd1187f014f19ac59a2313a4e6d644532b287d806fe87882ca47ea0d 
 = sha256sum file original
d74be01406764ef11f5aa3ee27033bdced2c9573c0b3ed980fcfb2116c7d84de  file
d74be01406764ef11f5aa3ee27033bdced2c9573c0b3ed980fcfb2116c7d84de  original
 =
2026-02-09 23:31:52 +01:00
Konmous04
260500f3a2
prune: added -v / --info output, fixes #9262
Some checks failed
CI / lint (push) Has been cancelled
CodeQL / Analyze (push) Has been cancelled
Windows CI / msys2-ucrt64 (push) Has been cancelled
CI / asan_ubsan (push) Has been cancelled
CI / native_tests (push) Has been cancelled
CI / vm_tests (Haiku, false, haiku, r1beta5) (push) Has been cancelled
CI / vm_tests (NetBSD, false, netbsd, 10.1) (push) Has been cancelled
CI / vm_tests (OpenBSD, false, openbsd, 7.7) (push) Has been cancelled
CI / vm_tests (borg-freebsd-14-x86_64-gh, FreeBSD, true, freebsd, 14.3) (push) Has been cancelled
2026-01-31 20:48:28 +01:00
TW
ea5a4a1a92
Merge pull request #9267 from ThomasWaldmann/FileType-1.4
Some checks failed
CI / lint (push) Has been cancelled
CodeQL / Analyze (push) Has been cancelled
Windows CI / msys2-ucrt64 (push) Has been cancelled
CI / asan_ubsan (push) Has been cancelled
CI / native_tests (push) Has been cancelled
CI / vm_tests (Haiku, false, haiku, r1beta5) (push) Has been cancelled
CI / vm_tests (NetBSD, false, netbsd, 10.1) (push) Has been cancelled
CI / vm_tests (OpenBSD, false, openbsd, 7.7) (push) Has been cancelled
CI / vm_tests (borg-freebsd-14-x86_64-gh, FreeBSD, true, freebsd, 14.3) (push) Has been cancelled
replace deprecated `argparse.FileType`, fixes #9266
2026-01-22 17:34:41 +01:00
Thomas Waldmann
3630eaafdf
replace deprecated argparse.FileType, fixes #9266
also: fix trailing whitespace in archiver.py.
2026-01-22 17:10:17 +01:00
Thomas Waldmann
000a5818ef
Version: do not access private attributes, fixes #9263
DeprecationWarning: Version._version is private and will be removed soon
2026-01-22 15:57:55 +01:00
MartinKurtz
f379420e7c
mount: warn about symlinks pointing outside of the mountpoint, see #9254
Some checks failed
CI / lint (push) Has been cancelled
CodeQL / Analyze (push) Has been cancelled
Windows CI / msys2-ucrt64 (push) Has been cancelled
CI / asan_ubsan (push) Has been cancelled
CI / native_tests (push) Has been cancelled
CI / vm_tests (Haiku, false, haiku, r1beta5) (push) Has been cancelled
CI / vm_tests (NetBSD, false, netbsd, 10.1) (push) Has been cancelled
CI / vm_tests (OpenBSD, false, openbsd, 7.7) (push) Has been cancelled
CI / vm_tests (borg-freebsd-14-x86_64-gh, FreeBSD, true, freebsd, 14.3) (push) Has been cancelled
2026-01-12 23:09:34 +01:00
Thomas Waldmann
4eed3ecd5b
fuse: add thread/async safety warning 2026-01-03 19:47:15 +01:00
Thomas Waldmann
646f279062
mount: performance improvement
### Performance Comparison: `bytearray.extend()` vs. Concatenation

The efficiency difference between `meta.extend(bytes(N))` and `meta = meta + bytes(N)` stems from how Python manages memory and objects during these operations.

#### 1. In-Place Modification vs. Object Creation
- **`bytearray.extend()`**: This is an **in-place** operation. If the current memory block allocated for the `bytearray` has enough extra capacity (pre-allocated space), Python simply writes the new bytes into that space and updates the length. If it needs more space, it uses `realloc()`, which can often expand the existing memory block without moving the entire data set to a new location.
- **Concatenation (`+`)**: This creates a **completely new** `bytearray` object. It allocates a new memory block large enough to hold the sum of both parts, copies the contents of `meta`, copies the contents of `bytes(N)`, and then reassigns the variable `meta` to this new object.

#### 2. Computational Complexity
- **`bytearray.extend()`**: In the best case (when capacity exists), it is **O(K)**, where K is the number of bytes being added. In the worst case (reallocation), it is **O(N + K)**, but Python uses an over-allocation strategy (growth factor) that amortizes this cost, making it significantly faster on average.
- **Concatenation (`+`)**: It is always **O(N + K)** because it must copy the existing `N` bytes every single time. As the `bytearray` grows larger (e.g., millions of items in a backup), this leads to **O(N²)** total time complexity across multiple additions, as you are repeatedly copying an ever-growing buffer.

#### 3. Memory Pressure and Garbage Collection
- Concatenation briefly requires memory for **both** the old buffer and the new buffer simultaneously before the old one is garbage collected. This increases the peak memory usage of the process.
- `extend()` is more memory-efficient as it minimizes the need for multiple large allocations and relies on the underlying memory manager's ability to resize buffers efficiently.

In the context of `borg mount`, where `meta` can grow to be many megabytes or even gigabytes for very large repositories, using concatenation causes a noticeable slowdown as the number of archives or files increases, whereas `extend()` remains performant.
2026-01-03 19:31:54 +01:00
Thomas Waldmann
8334b41f82
diff help: remove trailing blank 2025-12-02 00:19:23 +01:00
Thomas Waldmann
32946a9f81
compact: also fix segments hints data for lost segment files
The code used to remove the missing segment only from "compact" hints,
but we need to also remove it from "segments" hints.
2025-12-01 23:12:32 +01:00
Thomas Waldmann
3a78a3cf49
compact_segments: replace AssertionError by warning, fixes #8535
emit only a warning, but let compaction complete.
after that, borg check --repair can fix the hints successfully.

This is the result of a longer discussion with Antigravity AI and me:

Detailed Explanation: Why Converting AssertionError to Warning is Correct
=========================================================================

PROBLEM OVERVIEW
----------------
The assertion `assert segments[segment] == 0` in compact_segments() was causing
borg compact to crash when segment reference counts in the hints file didn't
match the actual repository state. This typically occurred after index corruption
or repository recovery scenarios.

ROOT CAUSE ANALYSIS
-------------------
The crash happens due to a fundamental mismatch between two data structures:

1. self.segments (loaded from hints file)
   - Contains reference counts for each segment
   - Persisted to disk in the hints file
   - Represents the "last known state"

2. self.index (loaded from index file)
   - Contains mappings of object IDs to (segment, offset) pairs
   - Can be corrupted or lost
   - When corrupted, triggers auto-recovery

The Problem Scenario:
1. Repository has valid data with consistent hints.N and index.N
2. Index file gets corrupted (crash, disk error, etc.)
3. Borg detects corruption and auto-recovers:
   - Loads hints.N (with old reference counts)
   - Rebuilds index by replaying segments
   - Commits the rebuilt index
4. State is now inconsistent IF segments were deleted/lost:
   - self.segments[X] = 10 (from old hints, assumes segment X exists)
   - Segment X was actually deleted/lost
   - self.index has 0 entries for segment X (rebuilt from remaining segments)
5. During compact_segments():
   - Tries to iterate objects in segment X
   - Segment X doesn't exist (was deleted/lost)
   - OR: segment X exists but objects aren't in index (superseded)
   - segments[X] is never decremented
   - segments[X] remains 10 instead of becoming 0
   - Assertion fails!

WHY THE FIX IS CORRECT
----------------------

1. Hints are Advisory, Not Authoritative
   The hints file is an optimization to avoid scanning all segments. It's
   explicitly designed to be rebuildable from scratch by scanning segments.
   Therefore, incorrect hints should not cause a fatal error.

2. Self-Healing Behavior
   By converting the assertion to a warning and allowing compaction to proceed:
   - Compaction completes successfully
   - New hints are written with correct reference counts
   - Repository is automatically healed
   - No manual intervention required

3. Data Safety is Preserved
   The fix does NOT compromise data integrity because:
   - Compaction first copies all live data from segments to new segments
   - Only after all live data is safely copied are segments marked for deletion
   - The index determines what's "live" (authoritative source of truth)
   - Segments are deleted only when they contain no live data (per index)
   - The refcount warning indicates stale hints, not actual data loss risk
   - After compaction, new hints are written with correct counts

4. Consistent with Design Philosophy
   Borg already handles many corruption scenarios gracefully:
   - Missing hints → regenerated from segments
   - Corrupted index → rebuilt from segments
   - Missing segments → detected and handled
   This fix extends that philosophy to hint/index mismatches.

5. Alternative Solutions are Worse
   Other approaches considered:
   a) Crash and require manual intervention
      - Current behavior, user-hostile
      - Requires expert knowledge to fix
   b) Automatically run check --repair
      - Too aggressive, may hide real problems
      - User should decide when to repair
   c) Refuse to compact
      - Leaves repository in degraded state
      - Prevents normal operations

VERIFICATION
------------
The fix has been verified with test cases that reproduce both scenarios:

1. test_missing_segment_in_hints
   - Simulates missing segment files
   - Verifies compact succeeds and updates hints correctly

2. test_index_corruption_with_old_hints
   - Simulates the root cause: corrupted index with old hints
   - Verifies compact succeeds despite reference count mismatch

3. test_subtly_corrupted_hints_without_integrity
   - Existing test updated to expect warning instead of crash
   - Verifies repository remains consistent after compaction

OPERATIONAL IMPACT
------------------
After this fix:
1. Users experiencing this crash can now run `borg compact` successfully
2. The warning message alerts them to the inconsistency
3. They can optionally run `borg check --repair` for peace of mind
4. Repository continues to function normally

The warning message provides enough information for debugging while not
blocking normal operations.

CONCLUSION
----------
Converting the assertion to a warning is the correct fix because:
- It aligns with Borg's design philosophy of graceful degradation
- It enables self-healing behavior
- It preserves data safety
- It improves user experience
- It's consistent with how other corruption scenarios are handled

The assertion was overly strict for a data structure (hints) that is
explicitly designed to be advisory and rebuildable.
2025-12-01 23:11:28 +01:00
Thomas Waldmann
3226b5b084
add test for issue #8535 2025-12-01 20:27:57 +01:00
Thomas Waldmann
af707b802e
freebsd/netbsd: skip some tests we have github issues for
skip test_hard_link_deletion_and_replacement, #9147, #9153

The test fails on these platforms.

I could not find the root cause of this issue, but it is likely a minor
problem with ctime and doesn't affect borg usage much.

So I rather like to have CI on freebsd/netbsd not failing because of this.

tests(diff): on NetBSD only expect mtime for touched file in JSON diff
(treat like Windows); completes backport of #9161 to 1.4-maint layout

tests(diff): also skip DiffArchiverTestCase.test_multiple_link_exclusion
when hardlinks unsupported (include are_hardlinks_supported in skip)
2025-11-21 23:46:27 +01:00
Thomas Waldmann
0a553ddc9f
testsuite: improve haiku compatibility
haiku does not have os.mknod.
and it looks like it does not have hardlinks either.
2025-11-21 22:21:54 +01:00
Thomas Waldmann
ea6450bf6f
tests: use context manager when opening files in patterns_test 2025-11-21 19:13:56 +01:00
Thomas Waldmann
e06935f275
add granularity_sleep, fixes #9150 2025-11-21 18:28:50 +01:00
Thomas Waldmann
cb0157ca55
drop Python 3.9 (EOL'ed by python.org) 2025-11-01 13:09:28 +01:00
Thomas Waldmann
6e19159385
run test_extract_restores_append_flag also on freebsd 2025-10-31 18:49:10 +01:00
TW
91a360496f
Merge pull request #9116 from ThomasWaldmann/fix/freebsd-flags-9090
Some checks are pending
CI / lint (push) Waiting to run
CI / pytest (push) Blocked by required conditions
CodeQL / Analyze (push) Waiting to run
Windows CI / msys2-ucrt64 (push) Waiting to run
extract: fs flags: use get/set to influence only specific flags, #9090, FreeBSD only
2025-10-31 01:57:45 +01:00
Thomas Waldmann
8a895f55ae
extract: fs flags: use get/set to influence only specific flags, #9090, FreeBSD only. 2025-10-31 00:41:52 +01:00
Thomas Waldmann
83571aa00d
extract: fs flags: use get/set to influence only specific flags, #9039, macOS only.
preserve UF_COMPRESSED and SF_DATALESS when restoring flags,
get-modify-set in macOS set_flags, keeping system-managed read-only flags.
2025-10-30 23:09:33 +01:00
Thomas Waldmann
177f7d5b9a
docs(compact): explain how to get maximum compaction with --threshold 0 and trade-offs (#9112, discussion #8716) 2025-10-30 11:08:17 +01:00
TW
83d9d28fa4
Merge pull request #9103 from PhrozenByte/docs-repokey-change-passphrase
Some checks are pending
CI / lint (push) Waiting to run
CI / pytest (push) Blocked by required conditions
CodeQL / Analyze (push) Waiting to run
Windows CI / msys2-ucrt64 (push) Waiting to run
Docs: Rewrite `borg init --encryption` docs
2025-10-29 19:24:45 +01:00
Thomas Waldmann
9226d9c6a9
reformat init_epilog to 80chars wide 2025-10-29 13:26:31 +01:00
Thomas Waldmann
b22cc77453
better clarity, generalisation, precision, less typos 2025-10-29 13:10:45 +01:00
Thomas Waldmann
3c2daef474
fix typos and grammar
Junie AI + TW
2025-10-29 11:19:49 +01:00
Thomas Waldmann
4eb6773c2d
fix rst_plain_text_references 2025-10-29 10:52:56 +01:00
Thomas Waldmann
2d63dc9a4f
BORG_MSGPACK_VERSION_CHECK=no to disable the version check, fixes #9109 2025-10-28 23:20:53 +01:00
Thomas Waldmann
0c27bd3047
json: include archive keys in JSON lines when requested via --format, fixes #9095
Add a test to ensure correct behavior.
2025-10-27 22:04:01 +01:00
Thomas Waldmann
ce27418791
create/info: fix discrepancies in archive stats, fixes #8898, #9003
do not account archive metadata, only file contents.
2025-10-27 21:36:37 +01:00
Thomas Waldmann
ff35e07997
tests: add tests for stats consistency, #8898 #9003
add tests ensuring:
- borg info and create report same "This archive" deduplicated size
- borg info and create report same "All archives" deduplicated size
- before/after borg recreate it reports same "This archive" deduplicated size

note that some stats differences are expected, because the repo-level
deduplication ("All archives") is computed in a different way than "This archive".
2025-10-27 21:35:08 +01:00
Daniel Rudolf
d42453fde4
Docs: Rewrite borg init docs 2025-10-26 23:13:59 +01:00
Thomas Waldmann
56dda84162
set_flags: remove compression flag
This flag needs to be set BEFORE writing to the file.
But "borg extract" sets the flags last (to support IMMUTABLE),
thus the compression flag would not work as expected.
2025-10-17 02:41:53 +02:00
Thomas Waldmann
9c600a9571
set_flags: better give up than corrupt
Thanks to Earnestly for the feedback on IRC.
2025-10-17 00:27:56 +02:00
Thomas Waldmann
9214197a2c
set_flags: use get/set to only influence specific flags, fixes #9039
Linux platform only.
2025-10-16 21:59:28 +02:00
Atemu
908c506e3f
extract: document how to use wildcards in PATHs
Fixes https://github.com/borgbackup/borg/issues/8589
2025-10-13 19:54:49 +02:00
TW
924d94be75
Merge pull request #9054 from ThomasWaldmann/fix-7144-1.4
Some checks are pending
CI / lint (push) Waiting to run
CI / pytest (macos-14, 3.11, py311-none) (push) Blocked by required conditions
CI / pytest (ubuntu-22.04, 3.10, py310-fuse3) (push) Blocked by required conditions
CI / pytest (ubuntu-22.04, 3.11, py311-fuse2) (push) Blocked by required conditions
CI / pytest (ubuntu-22.04, 3.9, py39-fuse2) (push) Blocked by required conditions
CI / pytest (ubuntu-24.04, 3.12, py312-fuse3) (push) Blocked by required conditions
CI / pytest (ubuntu-24.04, 3.13, py313-fuse3) (push) Blocked by required conditions
CI / pytest (ubuntu-24.04, 3.14, py314-fuse3) (push) Blocked by required conditions
CodeQL / Analyze (push) Waiting to run
docs: improve borg help patterns, fixes #7144
2025-10-12 15:24:42 +02:00
TW
81b3b767be
Merge pull request #9057 from ThomasWaldmann/mount-show-rc-1.4
mount --show-rc: display main process rc, fixes #8308
2025-10-12 15:19:11 +02:00
TW
cb19d62057
Merge pull request #9055 from ThomasWaldmann/non-tty-progress-1.4
improved tty-less progress reporting (1.4-maint)
2025-10-12 15:18:09 +02:00
Thomas Waldmann
41bd6dd35b
docs: improve borg help patterns, fixes #7144 2025-10-12 14:21:37 +02:00
Thomas Waldmann
f4a6ea370b
patterns: clarify scope of default pattern style, fixes #9004 2025-10-11 20:07:26 +02:00
Thomas Waldmann
702de47197
mount --show-rc: display main process rc, fixes #8308
when borg mount is used without -f/--foreground (so that the FUSE
borg process was started daemonized in the background), it did not
display the rc of the main process, even when --show-rc was used.

now it does display the rc of the main process.

note that this is rather a consistency fix than being super useful,
because the main "action" happens in the background daemon process,
not in the main process.
2025-10-11 18:55:54 +02:00
Thomas Waldmann
3e2f560a61
improved tty-less progress reporting (1.4-maint)
Previously when running borg in a systemd service (and similar when piping to
a file and co.), these problems occurred:

- The carriage return both made it so that journald interpreted the output as
  binary, therefore not printing the text, while also not buffering
  correctly, so that log output was only available every once in a while
  in the form [40k blob data]. This can partially be worked around by
  using `journalctl -a` to view the logs, which at least prints the text,
  though only sporadically.

- The path was getting truncated to a short length, since the default
  get_terminal_size returns a column width of 80, which isn't relevant
  when printing to e.g. journald.

This commit fixes this by introducing a new code path for when stream is
not a tty, which always prints the full paths and ends lines with a linefeed.

This is based on unfinished PR #8939 by @infinisil, thanks for your suggestion!
2025-10-11 14:17:02 +02:00
TW
98b831ba8d
Merge pull request #9049 from ThomasWaldmann/keyfile-same-repo-path-1.4
Some checks failed
CI / lint (push) Has been cancelled
CodeQL / Analyze (push) Has been cancelled
CI / pytest (macos-14, 3.11, py311-none) (push) Has been cancelled
CI / pytest (ubuntu-22.04, 3.10, py310-fuse3) (push) Has been cancelled
CI / pytest (ubuntu-22.04, 3.11, py311-fuse2) (push) Has been cancelled
CI / pytest (ubuntu-22.04, 3.9, py39-fuse2) (push) Has been cancelled
CI / pytest (ubuntu-24.04, 3.12, py312-fuse3) (push) Has been cancelled
CI / pytest (ubuntu-24.04, 3.13, py313-fuse3) (push) Has been cancelled
CI / pytest (ubuntu-24.04, 3.14, py314-fuse3) (push) Has been cancelled
docs: What happens when a new keyfile repo is created at the same path?
2025-10-10 20:25:10 +02:00
Thomas Waldmann
92418883dc
docs: how to debug borg mount, fixes #5461 2025-10-10 14:57:25 +02:00
Thomas Waldmann
80818c1588
docs: What happens when a new keyfile repo is created at the same path?, fixes #6230
Also add a test.
2025-10-10 14:12:48 +02:00
Thomas Waldmann
d35ec93968
allow msgpack 1.1.2
1.1.2 provides wheels for py 3.14.
2025-10-09 17:38:10 +02:00
Thomas Waldmann
4edd0e6e9d
read_only CM: skip test if cmd_immutable is unsuccessful, fixes #9021 2025-09-22 11:17:16 +02:00