Skip to content

extract: refuse to extract into a non-empty directory, fixes #10057 - #10390

Merged
ThomasWaldmann merged 1 commit into
borgbackup:masterfrom
ThomasWaldmann:extract-nonempty-10057
Sep 19, 2026
Merged

ThomasWaldmann merged 1 commit into
borgbackup:masterfrom
ThomasWaldmann:extract-nonempty-10057

Conversation

@ThomasWaldmann

@ThomasWaldmann ThomasWaldmann commented Sep 19, 2026

Copy link
Copy Markdown
Member

Fixes #10057.

Problem

Extracting into a non-empty directory

  • replaces existing files that are in the way, and
  • leaves all other existing files as they are, so the result is a mix of the files that were there before and the extracted files - which might not be what the user wanted.

Running borg extract by accident in a directory that is in use (the issue's example: the home directory, with an archive made by borg create ... .) thus replaces current files by their archived state and blends the archive into the directory. The docs already said that only extracting into an empty directory is supported, but nothing enforced it.

Change

  • borg extract aborts with the new Archive.ExtractionDirNotEmpty (rc 33) before writing anything if the current directory is not empty, unless --continue is given:

    Extraction directory /home/user is not empty. Use --continue to extract into a non-empty directory: existing files that differ from the archived files will be replaced and the result will be a mix of existing and extracted files.
    
  • same rule when only a part of the archive is extracted (PATH arguments / patterns): replacing one file in place is as destructive as replacing many.

  • --dry-run and --stdout do not write into the directory, so they are not checked.

  • the check is strict (any directory entry counts, no special cases like lost+found or .DS_Store). It costs one os.listdir call.

  • no new option, no prompt: --continue already is the way to extract into a directory that has contents. It is not a forced overwrite though - files with the same type/mode, size and mtime are skipped - so the docs now say that a file that was damaged without a change of size and mtime (bit rot) has to be removed before it can be extracted again.

This is a behaviour change for scripts and frontends that extract into a non-empty directory: they need to add --continue (or use an empty directory). It needs a compatibility note in the change log (not part of this PR).

The --overwrite=MODE family suggested in the issue is not needed for this and can still be added later (--continue would then be changed).

Docs

  • extract epilog and --continue help: describe the refusal, the mix of existing and extracted files, and what --continue really does.
  • quickstart restore example, FAQ ("other known limitations", "unstable SSH connection": mention --continue, which the second and later partial extracts into one directory now need), usage examples.
  • docs/internals/frontends.rst: new error in the error list (as generated by scripts/errorlist.py).

Tests

  • new test_extract_non_empty_dir: refusal for full and partial extraction (nothing extracted, existing file untouched), --dry-run/--stdout still work, --continue extracts and replaces.
  • 11 existing tests extracted repeatedly into the same directory (or, in two cases, into the test's non-empty working directory). They now use --continue, except:
    • test_verify_data_wrong_chunk_content: the second extraction must really read the file again, --continue would skip it, so it extracts into a fresh directory.
    • test_recreate_subtree_hardlinks: extracts the second archive into a fresh directory (with --continue it would depend on the hard link fix in extract --continue: keep groups of hard links together #10387).

Related: #10387 fixes --continue breaking up groups of hard links, which matters more now that --continue is the way to extract into a non-empty directory. The two PRs are independent of each other.

🤖 Generated with Claude Code

@codecov

codecov Bot commented Sep 19, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 83.33333% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 88.16%. Comparing base (7a16a03) to head (1b7ff2b).
⚠️ Report is 5 commits behind head on master.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
src/borg/archiver/extract_cmd.py 80.00% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master   #10390      +/-   ##
==========================================
- Coverage   88.19%   88.16%   -0.03%     
==========================================
  Files         103      103              
  Lines       18822    18840      +18     
  Branches     2919     2924       +5     
==========================================
+ Hits        16600    16611      +11     
- Misses       1548     1553       +5     
- Partials      674      676       +2     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

…up#10057

Extracting into a non-empty directory replaces existing files that are in
the way and leaves all other existing files as they are, so the result is a
mix of existing and extracted files. Accidentally running "borg extract" in
a directory that is in use (like a home directory) thus replaced current
files by their archived state and blended the archive into the directory.

Now borg aborts with Archive.ExtractionDirNotEmpty (rc 33) before writing
anything if the current directory is not empty, unless --continue is given.
This also applies when only a part of the archive is extracted. --dry-run
and --stdout do not write into the directory, so they are not checked.

No new option: --continue already is the way to extract into a directory
that has contents. Its help and the extract docs now describe what it does
(skip files that are fully extracted already, replace other existing files)
and that a damaged file with unchanged size and mtime must be removed
before it can be extracted again.

Tests that extracted repeatedly into the same directory now use --continue
or a fresh directory.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@ThomasWaldmann
ThomasWaldmann merged commit b83318b into borgbackup:master Sep 19, 2026
26 of 28 checks passed
@ThomasWaldmann
ThomasWaldmann deleted the extract-nonempty-10057 branch September 19, 2026 13:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

extract: shall borg warn if cwd is not empty?

1 participant