mirror of
https://github.com/borgbackup/borg.git
synced 2026-06-09 08:51:54 -04:00
recreate: --target
This commit is contained in:
parent
88798ae949
commit
93b1cf3453
3 changed files with 42 additions and 9 deletions
|
|
@ -1331,10 +1331,10 @@ class ArchiveRecreater:
|
|||
self.interrupt = False
|
||||
self.errors = False
|
||||
|
||||
def recreate(self, archive_name, comment=None):
|
||||
def recreate(self, archive_name, comment=None, target_name=None):
|
||||
assert not self.is_temporary_archive(archive_name)
|
||||
archive = self.open_archive(archive_name)
|
||||
target, resume_from = self.create_target_or_resume(archive)
|
||||
target, resume_from = self.create_target_or_resume(archive, target_name)
|
||||
if self.exclude_if_present or self.exclude_caches:
|
||||
self.matcher_add_tagged_dirs(archive)
|
||||
if self.matcher.empty() and not self.recompress and not target.recreate_rechunkify and comment is None:
|
||||
|
|
@ -1344,7 +1344,8 @@ class ArchiveRecreater:
|
|||
self.process_items(archive, target, resume_from)
|
||||
except self.Interrupted as e:
|
||||
return self.save(archive, target, completed=False, metadata=e.metadata)
|
||||
return self.save(archive, target, comment)
|
||||
replace_original = target_name is None
|
||||
return self.save(archive, target, comment, replace_original=replace_original)
|
||||
|
||||
def process_items(self, archive, target, resume_from=None):
|
||||
matcher = self.matcher
|
||||
|
|
@ -1475,7 +1476,7 @@ class ArchiveRecreater:
|
|||
logger.debug('Copied %d chunks from a partially processed item', len(partial_chunks))
|
||||
return partial_chunks
|
||||
|
||||
def save(self, archive, target, comment=None, completed=True, metadata=None):
|
||||
def save(self, archive, target, comment=None, completed=True, metadata=None, replace_original=True):
|
||||
"""Save target archive. If completed, replace source. If not, save temporary with additional 'metadata' dict."""
|
||||
if self.dry_run:
|
||||
return completed
|
||||
|
|
@ -1487,8 +1488,9 @@ class ArchiveRecreater:
|
|||
'cmdline': archive.metadata[b'cmdline'],
|
||||
'recreate_cmdline': sys.argv,
|
||||
})
|
||||
archive.delete(Statistics(), progress=self.progress)
|
||||
target.rename(archive.name)
|
||||
if replace_original:
|
||||
archive.delete(Statistics(), progress=self.progress)
|
||||
target.rename(archive.name)
|
||||
if self.stats:
|
||||
target.end = datetime.utcnow()
|
||||
log_multi(DASHES,
|
||||
|
|
@ -1540,11 +1542,11 @@ class ArchiveRecreater:
|
|||
matcher.add(tag_files, True)
|
||||
matcher.add(tagged_dirs, False)
|
||||
|
||||
def create_target_or_resume(self, archive):
|
||||
def create_target_or_resume(self, archive, target_name=None):
|
||||
"""Create new target archive or resume from temporary archive, if it exists. Return archive, resume from path"""
|
||||
if self.dry_run:
|
||||
return self.FakeTargetArchive(), None
|
||||
target_name = archive.name + '.recreate'
|
||||
target_name = target_name or archive.name + '.recreate'
|
||||
resume = target_name in self.manifest.archives
|
||||
target, resume_from = None, None
|
||||
if resume:
|
||||
|
|
|
|||
|
|
@ -969,8 +969,11 @@ class Archiver:
|
|||
if recreater.is_temporary_archive(name):
|
||||
self.print_error('Refusing to work on temporary archive of prior recreate: %s', name)
|
||||
return self.exit_code
|
||||
recreater.recreate(name, args.comment)
|
||||
recreater.recreate(name, args.comment, args.target)
|
||||
else:
|
||||
if args.target is not None:
|
||||
self.print_error('--target: Need to specify single archive')
|
||||
return self.exit_code
|
||||
for archive in manifest.list_archive_infos(sort_by='ts'):
|
||||
name = archive.name
|
||||
if recreater.is_temporary_archive(name):
|
||||
|
|
@ -2036,6 +2039,8 @@ class Archiver:
|
|||
archive that is built during the operation exists at the same time at
|
||||
"<ARCHIVE>.recreate". The new archive will have a different archive ID.
|
||||
|
||||
With --target the original archive is not replaced, instead a new archive is created.
|
||||
|
||||
When rechunking space usage can be substantial, expect at least the entire
|
||||
deduplicated size of the archives using the previous chunker params.
|
||||
When recompressing approximately 1 % of the repository size or 512 MB
|
||||
|
|
@ -2081,6 +2086,10 @@ class Archiver:
|
|||
help='keep tag files of excluded caches/directories')
|
||||
|
||||
archive_group = subparser.add_argument_group('Archive options')
|
||||
archive_group.add_argument('--target', dest='target', metavar='TARGET', default=None,
|
||||
type=archivename_validator(),
|
||||
help='create a new archive with the name ARCHIVE, do not replace existing archive '
|
||||
'(only applies for a single archive)')
|
||||
archive_group.add_argument('--comment', dest='comment', metavar='COMMENT', default=None,
|
||||
help='add a comment text to the archive')
|
||||
archive_group.add_argument('--timestamp', dest='timestamp',
|
||||
|
|
|
|||
|
|
@ -1522,6 +1522,28 @@ class ArchiverTestCase(ArchiverTestCaseBase):
|
|||
self.cmd('init', self.repository_location, exit_code=1)
|
||||
assert not os.path.exists(self.repository_location)
|
||||
|
||||
def test_recreate_target_rc(self):
|
||||
self.cmd('init', self.repository_location)
|
||||
output = self.cmd('recreate', self.repository_location, '--target=asdf', exit_code=2)
|
||||
assert 'Need to specify single archive' in output
|
||||
|
||||
def test_recreate_target(self):
|
||||
self.create_test_files()
|
||||
self.cmd('init', self.repository_location)
|
||||
archive = self.repository_location + '::test0'
|
||||
self.cmd('create', archive, 'input')
|
||||
original_archive = self.cmd('list', self.repository_location)
|
||||
self.cmd('recreate', archive, 'input/dir2', '-e', 'input/dir2/file3', '--target=new-archive')
|
||||
archives = self.cmd('list', self.repository_location)
|
||||
assert original_archive in archives
|
||||
assert 'new-archive' in archives
|
||||
|
||||
archive = self.repository_location + '::new-archive'
|
||||
listing = self.cmd('list', '--short', archive)
|
||||
assert 'file1' not in listing
|
||||
assert 'dir2/file2' in listing
|
||||
assert 'dir2/file3' not in listing
|
||||
|
||||
def test_recreate_basic(self):
|
||||
self.create_test_files()
|
||||
self.create_regular_file('dir2/file3', size=1024 * 80)
|
||||
|
|
|
|||
Loading…
Reference in a new issue