copy: copy an archive to a new archive name, #2300 - #10221
Open
ThomasWaldmann wants to merge 1 commit into
Open
Conversation
…p#2300 Copying is cheap and fast: no file content is read or written, only a new archive metadata object is created. Like any deduplicated archives, the two archives share their data, so a copy needs almost no additional repository space. The copy is an independent archive: deleting either of the two archives keeps the other one intact, because "borg compact" only frees chunks that no remaining archive references. Archive.copy() is Archive.rename() without removing the original archive entry. Copying an archive to its own name is refused: the new metadata would be identical, thus have the same archive ID, and no second archive would be created. OLDNAME must match precisely one archive, so it accepts an archive name (if it is unique) or an archive ID like "aid:d34db33f". Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ThomasWaldmann
force-pushed
the
archive-copy-2300
branch
from
August 27, 2026 20:08
0fc1c1e to
9b89c94
Compare
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #10221 +/- ##
==========================================
+ Coverage 87.29% 87.31% +0.02%
==========================================
Files 102 103 +1
Lines 18444 18480 +36
Branches 2834 2835 +1
==========================================
+ Hits 16100 16136 +36
Misses 1638 1638
Partials 706 706 ☔ View full report in Codecov by Harness. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds a
borg copy OLDNAME NEWNAMEcommand, addressing #2300 (the "additional names for an existing archive" request).Rationale
#2300 asked for archive aliases, sketched as a name -> archive dict in the manifest. In borg2 that design is both impossible and unnecessary:
archives/<hex-id>entry in borgstore, keyed by the id of the archive metadata object, and the name lives inside that id-hashed object. Two names cannot point at one archive object.borg compactonly frees chunks that no remaining archive references, so two archives sharing all their chunks via dedup already behave the way the issue wanted -- deleting one leaves the other fully intact, with no dangling-pointer failure mode that a symlink-style alias would have.So instead of an alias, this creates a real, independent second archive -- which costs almost nothing.
Implementation
Archive.copy()isArchive.rename()without removing the original archive entry: it writes a new archive metadata object (viaset_meta("name", ...)) and leaves the old directory entry in place. No file content is read or written, anditem_ptrsis carried over unchanged, so the item metadata stream and every content chunk are shared.The command is modelled on
rename_cmd.pyand uses@with_archive, so OLDNAME resolution (name if unique, oraid:<hex>prefix) andaid:-aware shell completion come for free.Copying an archive to its own name is refused: the new metadata would be byte-identical, hence the same archive id, hence it would just overwrite the existing directory entry -- a silent no-op rather than a copy.
Measured behaviour
On a repo with a single 2 MB random-data archive:
borg createborg copyborg delete <original>+borg compactA control run without the copy drops to 24 KB at the last step, so
compactreally is freeing the data in the absence of a second referrer. After deleting the original,borg checkis clean and the copy extracts byte-identical.Tests
Seven tests in
src/borg/testsuite/archiver/copy_cmd_test.py: basic copy, copy byaid:, shareditem_ptrs/timestamp, delete-original-then-compact-then-extract, copying into an existing archive series, copy-to-same-name refused, ambiguous OLDNAME refused.Notes for review
docs/man/borg-copy.1is included (following theborg analyzecommit); the date-only driftbuild_manproduces in all the other man pages is not.