Skip to content

extract --continue: keep groups of hard links together - #10387

Merged
ThomasWaldmann merged 1 commit into
borgbackup:masterfrom
ThomasWaldmann:extract-continue-hardlinks
Sep 19, 2026
Merged

ThomasWaldmann merged 1 commit into
borgbackup:masterfrom
ThomasWaldmann:extract-continue-hardlinks

Conversation

@ThomasWaldmann

Copy link
Copy Markdown
Member

borg extract --continue could break up a group of hard links.

The bug

When --continue finds that a file already is fully extracted (same type/mode, size and mtime), extract_item() returned right away - before the hard link manager learned the path of that file. If that file was the first item of a group of hard links, a following item of the same group found no link target and was extracted as a separate file (own inode, own copy of the content).

Reproduction: archive a, b, c (hard links of each other), extract, rm c (simulating an extraction that was interrupted before c), borg extract --continue:

after full extract:        733497944 733497944 733497944
after rm c + --continue:   733497944 733497944 733497954   <- c is a separate file

The fix

On the "already extracted" path of --continue, for items that are part of a group of hard links:

  • the first item of the group is remembered as the link target (as it would be if it was extracted), so the following items of the group get hard linked to it.
  • a following item is only left alone if it already is a hard link to the group's link target (os.path.samestat). Otherwise it is replaced by a hard link to it. This covers the opposite order: the first item of the group is missing and had to be extracted again, while another link of the group still exists - without this, the existing one would stay on the old inode.

Items without hlid and platforms without os.link behave as before.

Tests

test_extract_continue_hardlinks removes either the first or the last item of a group of 3 hard links (the order within the archive depends on the filesystem, so the test asks borg list for it) and checks that after extract --continue all 3 are hard links to one inode again, and that already extracted files are not extracted again. Both variants fail without the fix.

Found while working on #10057, where --continue is planned to become the way to extract into a non-empty directory.

🤖 Generated with Claude Code

When --continue skipped an already extracted file that is part of a group
of hard links, the hard link manager did not learn its path. A following
item of the same group was then extracted as a separate file instead of
being hard linked to the skipped one.

Now a skipped first item of a group is remembered as the link target, and a
skipped later item is only left alone if it already is a hard link to the
group's link target. Otherwise it is replaced by a hard link to it (this
happens if the first item of the group had to be extracted again).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@codecov

codecov Bot commented Sep 19, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 88.15%. Comparing base (7a16a03) to head (e120f0f).
⚠️ Report is 1 commits behind head on master.
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@            Coverage Diff             @@
##           master   #10387      +/-   ##
==========================================
- Coverage   88.19%   88.15%   -0.05%     
==========================================
  Files         103      103              
  Lines       18822    18829       +7     
  Branches     2919     2922       +3     
==========================================
- Hits        16600    16598       -2     
- Misses       1548     1555       +7     
- Partials      674      676       +2     

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

@ThomasWaldmann
ThomasWaldmann merged commit cc8b70e into borgbackup:master Sep 19, 2026
26 of 27 checks passed
@ThomasWaldmann
ThomasWaldmann deleted the extract-continue-hardlinks branch September 19, 2026 10:05
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.

1 participant