Merge pull request #1712 from ThomasWaldmann/mount-some

borg mount --first / --last / --sort / --prefix, fixes #1542
This commit is contained in:
TW 2016-10-18 01:22:10 +02:00 committed by GitHub
commit 64d128cf22
5 changed files with 59 additions and 40 deletions

View file

@ -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)

View file

@ -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"""

View file

@ -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)

View file

@ -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"""

View file

@ -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