mirror of
https://github.com/borgbackup/borg.git
synced 2026-05-28 04:03:21 -04:00
Merge pull request #1712 from ThomasWaldmann/mount-some
borg mount --first / --last / --sort / --prefix, fixes #1542
This commit is contained in:
commit
64d128cf22
5 changed files with 59 additions and 40 deletions
|
|
@ -1295,7 +1295,7 @@ class ArchiveChecker:
|
|||
|
||||
if archive is None:
|
||||
# we need last N or all archives
|
||||
archive_infos = self.manifest.archives.list(sort_by='ts', reverse=True)
|
||||
archive_infos = self.manifest.archives.list(sort_by=['ts'], reverse=True)
|
||||
if prefix is not None:
|
||||
archive_infos = [info for info in archive_infos if info.name.startswith(prefix)]
|
||||
num_archives = len(archive_infos)
|
||||
|
|
|
|||
|
|
@ -783,7 +783,7 @@ class Archiver:
|
|||
if args.location.archive:
|
||||
archive_names = (args.location.archive,)
|
||||
else:
|
||||
archive_names = tuple(x.name for x in self._get_filtered_archives(args, manifest))
|
||||
archive_names = tuple(x.name for x in manifest.archives.list_considering(args))
|
||||
if not archive_names:
|
||||
return self.exit_code
|
||||
|
||||
|
|
@ -825,7 +825,7 @@ class Archiver:
|
|||
else:
|
||||
msg.append("You requested to completely DELETE the repository *including* all archives it "
|
||||
"contains:")
|
||||
for archive_info in manifest.archives.list(sort_by='ts'):
|
||||
for archive_info in manifest.archives.list(sort_by=['ts']):
|
||||
msg.append(format_archive(archive_info))
|
||||
msg.append("Type 'YES' if you understand this and want to continue: ")
|
||||
msg = '\n'.join(msg)
|
||||
|
|
@ -853,12 +853,7 @@ class Archiver:
|
|||
return self.exit_code
|
||||
|
||||
with cache_if_remote(repository) as cached_repo:
|
||||
if args.location.archive:
|
||||
archive = Archive(repository, key, manifest, args.location.archive,
|
||||
consider_part_files=args.consider_part_files)
|
||||
else:
|
||||
archive = None
|
||||
operations = FuseOperations(key, repository, manifest, archive, cached_repo)
|
||||
operations = FuseOperations(key, repository, manifest, args, cached_repo)
|
||||
logger.info("Mounting filesystem")
|
||||
try:
|
||||
operations.mount(args.mountpoint, args.options, args.foreground)
|
||||
|
|
@ -909,7 +904,7 @@ class Archiver:
|
|||
format = "{archive:<36} {time} [{id}]{NL}"
|
||||
formatter = ArchiveFormatter(format)
|
||||
|
||||
for archive_info in self._get_filtered_archives(args, manifest):
|
||||
for archive_info in manifest.archives.list_considering(args):
|
||||
write(safe_encode(formatter.format_item(archive_info)))
|
||||
|
||||
return self.exit_code
|
||||
|
|
@ -929,7 +924,7 @@ class Archiver:
|
|||
if args.location.archive:
|
||||
archive_names = (args.location.archive,)
|
||||
else:
|
||||
archive_names = tuple(x.name for x in self._get_filtered_archives(args, manifest))
|
||||
archive_names = tuple(x.name for x in manifest.archives.list_considering(args))
|
||||
if not archive_names:
|
||||
return self.exit_code
|
||||
|
||||
|
|
@ -981,7 +976,7 @@ class Archiver:
|
|||
'"keep-secondly", "keep-minutely", "keep-hourly", "keep-daily", '
|
||||
'"keep-weekly", "keep-monthly" or "keep-yearly" settings must be specified.')
|
||||
return self.exit_code
|
||||
archives_checkpoints = manifest.archives.list(sort_by='ts', reverse=True) # just a ArchiveInfo list
|
||||
archives_checkpoints = manifest.archives.list(sort_by=['ts'], reverse=True) # just a ArchiveInfo list
|
||||
if args.prefix:
|
||||
archives_checkpoints = [arch for arch in archives_checkpoints if arch.name.startswith(args.prefix)]
|
||||
is_checkpoint = re.compile(r'\.checkpoint(\.\d+)?$').search
|
||||
|
|
@ -1099,7 +1094,7 @@ class Archiver:
|
|||
if args.target is not None:
|
||||
self.print_error('--target: Need to specify single archive')
|
||||
return self.exit_code
|
||||
for archive in manifest.archives.list(sort_by='ts'):
|
||||
for archive in manifest.archives.list(sort_by=['ts']):
|
||||
name = archive.name
|
||||
if recreater.is_temporary_archive(name):
|
||||
continue
|
||||
|
|
@ -2115,6 +2110,7 @@ class Archiver:
|
|||
help='stay in foreground, do not daemonize')
|
||||
subparser.add_argument('-o', dest='options', type=str,
|
||||
help='Extra mount options')
|
||||
self.add_archives_filters_args(subparser)
|
||||
|
||||
info_epilog = textwrap.dedent("""
|
||||
This command displays detailed information about the specified archive or repository.
|
||||
|
|
@ -2630,21 +2626,6 @@ class Archiver:
|
|||
logger.warning("Using a pure-python msgpack! This will result in lower performance.")
|
||||
return args.func(args)
|
||||
|
||||
def _get_filtered_archives(self, args, manifest):
|
||||
if args.location.archive:
|
||||
raise Error('The options --first, --last and --prefix can only be used on repository targets.')
|
||||
|
||||
archives = manifest.archives.list(prefix=args.prefix)
|
||||
|
||||
for sortkey in reversed(args.sort_by.split(',')):
|
||||
archives.sort(key=attrgetter(sortkey))
|
||||
if args.last:
|
||||
archives.reverse()
|
||||
|
||||
n = args.first or args.last or len(archives)
|
||||
|
||||
return archives[:n]
|
||||
|
||||
|
||||
def sig_info_handler(sig_no, stack): # pragma: no cover
|
||||
"""search the stack for infos about the currently processed file and print them"""
|
||||
|
|
|
|||
|
|
@ -58,11 +58,11 @@ class FuseOperations(llfuse.Operations):
|
|||
allow_damaged_files = False
|
||||
versions = False
|
||||
|
||||
def __init__(self, key, repository, manifest, archive, cached_repo):
|
||||
def __init__(self, key, repository, manifest, args, cached_repo):
|
||||
super().__init__()
|
||||
self.repository_uncached = repository
|
||||
self.repository = cached_repo
|
||||
self.archive = archive
|
||||
self.args = args
|
||||
self.manifest = manifest
|
||||
self.key = key
|
||||
self._inode_count = 0
|
||||
|
|
@ -79,11 +79,15 @@ class FuseOperations(llfuse.Operations):
|
|||
|
||||
def _create_filesystem(self):
|
||||
self._create_dir(parent=1) # first call, create root dir (inode == 1)
|
||||
if self.archive:
|
||||
self.process_archive(self.archive)
|
||||
if self.args.location.archive:
|
||||
archive = Archive(self.repository_uncached, self.key, self.manifest, self.args.location.archive,
|
||||
consider_part_files=self.args.consider_part_files)
|
||||
self.process_archive(archive)
|
||||
else:
|
||||
for name in self.manifest.archives:
|
||||
archive = Archive(self.repository_uncached, self.key, self.manifest, name)
|
||||
archive_names = (x.name for x in self.manifest.archives.list_considering(self.args))
|
||||
for name in archive_names:
|
||||
archive = Archive(self.repository_uncached, self.key, self.manifest, name,
|
||||
consider_part_files=self.args.consider_part_files)
|
||||
if self.versions:
|
||||
# process archives immediately
|
||||
self.process_archive(archive)
|
||||
|
|
|
|||
|
|
@ -142,17 +142,29 @@ class Archives(abc.MutableMapping):
|
|||
name = safe_encode(name)
|
||||
del self._archives[name]
|
||||
|
||||
def list(self, sort_by=None, reverse=False, prefix=''):
|
||||
def list(self, sort_by=(), reverse=False, prefix='', first=None, last=None):
|
||||
"""
|
||||
Inexpensive Archive.list_archives replacement if we just need .name, .id, .ts
|
||||
Returns list of borg.helpers.ArchiveInfo instances
|
||||
Returns list of borg.helpers.ArchiveInfo instances.
|
||||
sort_by can be a list of sort keys, they are applied in reverse order.
|
||||
"""
|
||||
if isinstance(sort_by, (str, bytes)):
|
||||
raise TypeError('sort_by must be a sequence of str')
|
||||
archives = [x for x in self.values() if x.name.startswith(prefix)]
|
||||
if sort_by is not None:
|
||||
archives = sorted(archives, key=attrgetter(sort_by))
|
||||
if reverse:
|
||||
for sortkey in reversed(sort_by):
|
||||
archives.sort(key=attrgetter(sortkey))
|
||||
if reverse or last:
|
||||
archives.reverse()
|
||||
return archives
|
||||
n = first or last or len(archives)
|
||||
return archives[:n]
|
||||
|
||||
def list_considering(self, args):
|
||||
"""
|
||||
get a list of archives, considering --first/last/prefix/sort cmdline args
|
||||
"""
|
||||
if args.location.archive:
|
||||
raise Error('The options --first, --last and --prefix can only be used on repository targets.')
|
||||
return self.list(sort_by=args.sort_by.split(','), prefix=args.prefix, first=args.first, last=args.last)
|
||||
|
||||
def set_raw_dict(self, d):
|
||||
"""set the dict we get from the msgpack unpacker"""
|
||||
|
|
|
|||
|
|
@ -1508,6 +1508,28 @@ class ArchiverTestCase(ArchiverTestCaseBase):
|
|||
with self.fuse_mount(self.repository_location + '::archive', mountpoint, '-o', 'allow_damaged_files'):
|
||||
open(os.path.join(mountpoint, path)).close()
|
||||
|
||||
@unittest.skipUnless(has_llfuse, 'llfuse not installed')
|
||||
def test_fuse_mount_options(self):
|
||||
self.cmd('init', self.repository_location)
|
||||
self.create_src_archive('arch11')
|
||||
self.create_src_archive('arch12')
|
||||
self.create_src_archive('arch21')
|
||||
self.create_src_archive('arch22')
|
||||
|
||||
mountpoint = os.path.join(self.tmpdir, 'mountpoint')
|
||||
with self.fuse_mount(self.repository_location, mountpoint, '--first=2', '--sort=name'):
|
||||
assert sorted(os.listdir(os.path.join(mountpoint))) == ['arch11', 'arch12']
|
||||
with self.fuse_mount(self.repository_location, mountpoint, '--last=2', '--sort=name'):
|
||||
assert sorted(os.listdir(os.path.join(mountpoint))) == ['arch21', 'arch22']
|
||||
with self.fuse_mount(self.repository_location, mountpoint, '--prefix=arch1'):
|
||||
assert sorted(os.listdir(os.path.join(mountpoint))) == ['arch11', 'arch12']
|
||||
with self.fuse_mount(self.repository_location, mountpoint, '--prefix=arch2'):
|
||||
assert sorted(os.listdir(os.path.join(mountpoint))) == ['arch21', 'arch22']
|
||||
with self.fuse_mount(self.repository_location, mountpoint, '--prefix=arch'):
|
||||
assert sorted(os.listdir(os.path.join(mountpoint))) == ['arch11', 'arch12', 'arch21', 'arch22']
|
||||
with self.fuse_mount(self.repository_location, mountpoint, '--prefix=nope'):
|
||||
assert sorted(os.listdir(os.path.join(mountpoint))) == []
|
||||
|
||||
def verify_aes_counter_uniqueness(self, method):
|
||||
seen = set() # Chunks already seen
|
||||
used = set() # counter values already used
|
||||
|
|
|
|||
Loading…
Reference in a new issue