|
@@ -347,72 +347,22 @@ class Archiver:
|
|
|
def do_transfer(self, args, *,
|
|
|
repository, manifest, key, cache,
|
|
|
other_repository=None, other_manifest=None, other_key=None):
|
|
|
- """archives transfer from other repository"""
|
|
|
-
|
|
|
- ITEM_KEY_WHITELIST = {'path', 'source', 'rdev', 'chunks', 'chunks_healthy', 'hlid',
|
|
|
- 'mode', 'user', 'group', 'uid', 'gid', 'mtime', 'atime', 'ctime', 'birthtime', 'size',
|
|
|
- 'xattrs', 'bsdflags', 'acl_nfs4', 'acl_access', 'acl_default', 'acl_extended',
|
|
|
- 'part'}
|
|
|
-
|
|
|
- def upgrade_item(item):
|
|
|
- """upgrade item as needed, get rid of legacy crap"""
|
|
|
- if hlm.borg1_hardlink_master(item):
|
|
|
- item._dict['hlid'] = hlid = hlm.hardlink_id_from_path(item._dict['path'])
|
|
|
- hlm.remember(id=hlid, info=(item._dict.get('chunks'), item._dict.get('chunks_healthy')))
|
|
|
- elif hlm.borg1_hardlink_slave(item):
|
|
|
- item._dict['hlid'] = hlid = hlm.hardlink_id_from_path(item._dict['source'])
|
|
|
- chunks, chunks_healthy = hlm.retrieve(id=hlid, default=(None, None))
|
|
|
- if chunks is not None:
|
|
|
- item._dict['chunks'] = chunks
|
|
|
- for chunk_id, _ in chunks:
|
|
|
- cache.chunk_incref(chunk_id, archive.stats)
|
|
|
- if chunks_healthy is not None:
|
|
|
- item._dict['chunks_healthy'] = chunks
|
|
|
- item._dict.pop('source') # not used for hardlinks any more, replaced by hlid
|
|
|
- # make sure we only have desired stuff in the new item. specifically, make sure to get rid of:
|
|
|
- # - 'acl' remnants of bug in attic <= 0.13
|
|
|
- # - 'hardlink_master' (superseded by hlid)
|
|
|
- new_item_dict = {key: value for key, value in item.as_dict().items() if key in ITEM_KEY_WHITELIST}
|
|
|
- new_item = Item(internal_dict=new_item_dict)
|
|
|
- new_item.get_size(memorize=True) # if not already present: compute+remember size for items with chunks
|
|
|
- assert all(key in new_item for key in REQUIRED_ITEM_KEYS)
|
|
|
- return new_item
|
|
|
-
|
|
|
- def upgrade_compressed_chunk(chunk):
|
|
|
- def upgrade_zlib_and_level(chunk):
|
|
|
- if ZLIB_legacy.detect(chunk):
|
|
|
- ctype = ZLIB.ID
|
|
|
- chunk = ctype + level + chunk # get rid of the attic legacy: prepend separate type/level bytes
|
|
|
- else:
|
|
|
- ctype = chunk[0:1]
|
|
|
- chunk = ctype + level + chunk[2:] # keep type same, but set level
|
|
|
- return chunk
|
|
|
-
|
|
|
- ctype = chunk[0:1]
|
|
|
- level = b'\xFF' # FF means unknown compression level
|
|
|
-
|
|
|
- if ctype == ObfuscateSize.ID:
|
|
|
- # in older borg, we used unusual byte order
|
|
|
- old_header_fmt = Struct('>I')
|
|
|
- new_header_fmt = ObfuscateSize.header_fmt
|
|
|
- length = ObfuscateSize.header_len
|
|
|
- size_bytes = chunk[2:2+length]
|
|
|
- size = old_header_fmt.unpack(size_bytes)
|
|
|
- size_bytes = new_header_fmt.pack(size)
|
|
|
- compressed = chunk[2+length:]
|
|
|
- compressed = upgrade_zlib_and_level(compressed)
|
|
|
- chunk = ctype + level + size_bytes + compressed
|
|
|
- else:
|
|
|
- chunk = upgrade_zlib_and_level(chunk)
|
|
|
- return chunk
|
|
|
-
|
|
|
+ """archives transfer from other repository, optionally upgrade data format"""
|
|
|
dry_run = args.dry_run
|
|
|
-
|
|
|
args.consider_checkpoints = True
|
|
|
archive_names = tuple(x.name for x in other_manifest.archives.list_considering(args))
|
|
|
if not archive_names:
|
|
|
return EXIT_SUCCESS
|
|
|
|
|
|
+ from . import upgrade as upgrade_mod
|
|
|
+ try:
|
|
|
+ UpgraderCls = getattr(upgrade_mod, f'Upgrader{args.upgrader}')
|
|
|
+ except AttributeError:
|
|
|
+ self.print_error(f'No such upgrader: {args.upgrader}')
|
|
|
+ return EXIT_ERROR
|
|
|
+
|
|
|
+ upgrader = UpgraderCls(cache=cache)
|
|
|
+
|
|
|
for name in archive_names:
|
|
|
transfer_size = 0
|
|
|
present_size = 0
|
|
@@ -421,9 +371,9 @@ class Archiver:
|
|
|
else:
|
|
|
if not dry_run:
|
|
|
print(f"{name}: copying archive to destination repo...")
|
|
|
- hlm = HardLinkManager(id_type=bytes, info_type=tuple) # hlid -> (chunks, chunks_healthy)
|
|
|
other_archive = Archive(other_repository, other_key, other_manifest, name)
|
|
|
archive = Archive(repository, key, manifest, name, cache=cache, create=True) if not dry_run else None
|
|
|
+ upgrader.new_archive(archive=archive)
|
|
|
for item in other_archive.iter_items():
|
|
|
if 'chunks' in item:
|
|
|
chunks = []
|
|
@@ -434,7 +384,7 @@ class Archiver:
|
|
|
cdata = other_repository.get(chunk_id)
|
|
|
# keep compressed payload same, avoid decompression / recompression
|
|
|
data = other_key.decrypt(chunk_id, cdata, decompress=False)
|
|
|
- data = upgrade_compressed_chunk(data)
|
|
|
+ data = upgrader.upgrade_compressed_chunk(chunk=data)
|
|
|
chunk_entry = cache.add_chunk(chunk_id, data, archive.stats, wait=False,
|
|
|
compress=False, size=size)
|
|
|
cache.repository.async_response(wait=False)
|
|
@@ -449,15 +399,9 @@ class Archiver:
|
|
|
item.chunks = chunks # TODO: overwrite? IDs and sizes are same.
|
|
|
archive.stats.nfiles += 1
|
|
|
if not dry_run:
|
|
|
- archive.add_item(upgrade_item(item))
|
|
|
+ archive.add_item(upgrader.upgrade_item(item=item))
|
|
|
if not dry_run:
|
|
|
- additional_metadata = {}
|
|
|
- # keep all metadata except archive version and stats. also do not keep
|
|
|
- # recreate_source_id, recreate_args, recreate_partial_chunks which were used only in 1.1.0b1 .. b2.
|
|
|
- for attr in ('cmdline', 'hostname', 'username', 'time', 'time_end', 'comment',
|
|
|
- 'chunker_params', 'recreate_cmdline'):
|
|
|
- if hasattr(other_archive.metadata, attr):
|
|
|
- additional_metadata[attr] = getattr(other_archive.metadata, attr)
|
|
|
+ additional_metadata = upgrader.upgrade_archive_metadata(metadata=other_archive.metadata)
|
|
|
archive.save(stats=archive.stats, additional_metadata=additional_metadata)
|
|
|
print(f"{name}: finished. "
|
|
|
f"transfer_size: {format_file_size(transfer_size)} "
|
|
@@ -4057,8 +4001,9 @@ class Archiver:
|
|
|
# borg transfer
|
|
|
transfer_epilog = process_epilog("""
|
|
|
This command transfers archives from one repository to another repository.
|
|
|
+ Optionally, it can also upgrade the transferred data.
|
|
|
|
|
|
- Suggested use::
|
|
|
+ Suggested use for general purpose archive transfer (not repo upgrades)::
|
|
|
|
|
|
# initialize DST_REPO reusing key material from SRC_REPO, so that
|
|
|
# chunking and chunk id generation will work in the same way as before.
|
|
@@ -4074,6 +4019,13 @@ class Archiver:
|
|
|
You could use the misc. archive filter options to limit which archives it will
|
|
|
transfer, e.g. using the -a option. This is recommended for big
|
|
|
repositories with multiple data sets to keep the runtime per invocation lower.
|
|
|
+
|
|
|
+ For repository upgrades (e.g. from a borg 1.2 repo to a related borg 2.0 repo), usage is
|
|
|
+ quite similar to the above::
|
|
|
+
|
|
|
+ borg --repo=DST_REPO transfer --other-repo=SRC_REPO --upgrader=From12To20
|
|
|
+
|
|
|
+
|
|
|
""")
|
|
|
subparser = subparsers.add_parser('transfer', parents=[common_parser], add_help=False,
|
|
|
description=self.do_transfer.__doc__,
|
|
@@ -4086,6 +4038,9 @@ class Archiver:
|
|
|
subparser.add_argument('--other-repo', metavar='SRC_REPOSITORY', dest='other_location',
|
|
|
type=location_validator(other=True), default=Location(other=True),
|
|
|
help='transfer archives from the other repository')
|
|
|
+ subparser.add_argument('--upgrader', metavar='UPGRADER', dest='upgrader',
|
|
|
+ type=str, default='NoOp',
|
|
|
+ help='use the upgrader to convert transferred data (default: no conversion)')
|
|
|
define_archive_filters_group(subparser)
|
|
|
|
|
|
# borg diff
|