Invalid subcommand synonyms/fuzzy hints, argv-based examples, Common fixes for repo::archive and flags before subcommand, repo-create encryption reminder.
prune: add --json option, fixes#9222
Enable programmatic extraction of prune/keep decisions via
structured JSON output, instead of parsing log message text.
Follows the repo-list --json pattern: outputs a single JSON object
with repository, encryption, and archives array. Each archive
includes pruned (bool), rule, and rule_number fields.
This adds a runtime warning when running under MSYS2/Git Bash without the necessary environment variables to disable automatic path translation. The documentation is also updated to explain this behavior and how to mitigate it.
- add `--tags TAG [TAG ...]` option to `borg create` to tag newly created archives.
- validate the tags exactly like `borg tag` does, including checking that any special tags starting with `@` are known `SPECIAL_TAGS`.
- add `test_create_tags` and `test_create_invalid_tags` to ensure proper behavior.
- Added `--hostname` and `--username` command-line options to `borg create`
- Updated Archive to capture and store these explicit values, falling back to system defaults
- Added `test_explicit_hostname_and_username` to verify the functionality
test_with_lock previously relied on a hardcoded timeout
(`time.sleep(4)`) to ensure the first background command acquired
the repository lock before the second command tried to get it. On
extremely slow CI runners, this was sometimes too short, allowing
the second command to falsely acquire the lock.
This commit replaces the arbitrary sleep with true synchronization:
- The first command now blocks indefinitely using `sys.stdin.read()`.
- The test deterministically waits for lock acquisition by reading
`p1.stdout.readline()` which guarantees the lock is held.
- After the second command correctly fails, the first command is
smoothly unblocked and terminated by passing `input=""` to
`p1.communicate()`.
Python's `os.truncate()` on Windows relies on `SetEndOfFile()`, which does
not initialize the extended disk space with zeroes. This means that
trailing sparse holes simply leave uninitialized garbage data at the end
of the file.
During sparse file extraction, when the very last chunk is a sparse hole,
the VDL (Valid Data Length) is not properly advanced by `os.truncate()`.
As a result, reading from the end of the file fetches random disk garbage
instead of zeroes, causing spurious test failures at boundaries (like
2MB or 8MB) depending on what was in the uninitialized disk sectors.
Fix this by tracking trailing holes and manually writing a single `b"\0"`
byte at the end of the file before truncating on Windows. Writing explicit
data forces NTFS to officially advance the VDL and securely zero-fill the
preceding hole space.
Re-enable `test_sparse_file` on Windows.
This adds the `--paths-from-shell-command` option to the `create` command, enabling the use of shell-specific features like pipes and redirection when specifying input paths. Includes related test coverage.
Borg's ArgumentParser (in borg.helpers.argparsing) now subclasses
jsonargparse's ArgumentParser and pre-sets two defaults that every
borg parser uses:
formatter_class = RawDescriptionHelpFormatter
add_help = False
The old code worked around argparse's flat namespace by appending
_maincommand / _midcommand / _subcommand suffixes to every common
option's dest (e.g. log_level_subcommand), then resolving them with
CommonOptions.resolve() after parsing. This polluted config key names
and env var names (BORG_LOG_LEVEL_SUBCOMMAND instead of BORG_LOG_LEVEL).
jsonargparse nests subcommand arguments automatically, so the workaround
is no longer needed. Each parser level now registers common options with
their clean dest name. flatten_namespace() is updated to a two-pass
depth-first walk so the most-specific (innermost) value wins naturally:
borg --info create --debug → log_level = "debug" (subcommand wins)
borg --info create → log_level = "info" (top-level fills gap)
For append-action options (--debug-topic) values from all levels are
merged (outer + inner) to preserve the accumulation behaviour.
Previously, ArgparsePatternAction and ArgparsePatternFileAction
appended recursion roots directly to args.paths. This mixed
CLI positional paths with paths derived from patterns
(e.g., using the `R` root path command in a pattern file),
complicating downstream argument parsing and future jsonargparse
integration.
This commit introduces `args.pattern_roots` as a dedicated list
for these accumulated root paths:
- All argparse definition sites now initialize `pattern_roots=[]` alongside `paths=[]`
- ArgparsePatternAction and ArgparsePatternFileAction write directly to `args.pattern_roots`
- The build_matcher utility accepts both `include_paths` and `pattern_roots` and concatenates them internally
- `create_cmd` iterations explicitly concatenate both lists before processing
This ensures `args.paths` strictly reflects exactly what the
user provided positionally, paving the way for a clean
jsonargparse implementation without regressions in pattern behavior.
there are a lot of files in src_dir (due to the __pycache__ subdir).
for tests that do not need that, we can use a much smaller set of files,
now provided by the backup_files fixture.
time: the nominal ts, used for prune, list, sorting, ...
start: operation start time (informative)
end: operation end time (informative)
Often, "time" is the same as "start" (normal borg create).
But it can make sense to have a different "time":
- borg create --timestamp=...
- borg recreate --timestamp=...
- borg recreate (will keep "time" as in original archive)
- borg transfer (will keep "time" as in original archive)
recreate and transfer produce new archives, "start" and "end"
will reflect the recreate/transfer operation.
Also: remove start_monotonic. start and end are just what the
clock shows (including tz), so should be ok to compute duration
from that, even for dst switching times.