]> arthur.barton.de Git - bup.git/blobdiff - cmd/restore-cmd.py
Adjust restore-cmd for python 3 and then enable test-meta
[bup.git] / cmd / restore-cmd.py
index 23a0d4d8a6223a9f233197139e6056df906e61ac..a5993638eaab1cb3380892953661c8e27907c1e2 100755 (executable)
@@ -5,22 +5,24 @@ exec "$bup_python" "$0" ${1+"$@"}
 """
 # end of bup preamble
 
-from __future__ import print_function
+from __future__ import absolute_import
 from stat import S_ISDIR
 import copy, errno, os, sys, stat, re
 
-from bup import options, git, metadata, vfs2
+from bup import options, git, metadata, vfs
 from bup._helpers import write_sparsely
-from bup.compat import wrap_main
+from bup.compat import argv_bytes, fsencode, wrap_main
 from bup.helpers import (add_error, chunkyreader, die_if_errors, handle_ctrl_c,
                          log, mkdirp, parse_rx_excludes, progress, qprogress,
                          saved_errors, should_rx_exclude_path, unlink)
-from bup.repo import LocalRepo
+from bup.io import byte_stream
+from bup.repo import LocalRepo, RemoteRepo
 
 
 optspec = """
-bup restore [-C outdir] </branch/revision/path/to/dir ...>
+bup restore [-r host:path] [-C outdir] </branch/revision/path/to/dir ...>
 --
+r,remote=   remote repository path
 C,outdir=   change to given outdir before extracting files
 numeric-ids restore numeric IDs (user, group, etc.) rather than names
 exclude-rx= skip paths matching the unanchored regex (may be repeated)
@@ -37,30 +39,34 @@ q,quiet     don't show progress meter
 total_restored = 0
 
 # stdout should be flushed after each line, even when not connected to a tty
+stdoutfd = sys.stdout.fileno()
 sys.stdout.flush()
-sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 1)
+sys.stdout = os.fdopen(stdoutfd, 'w', 1)
+out = byte_stream(sys.stdout)
 
 def valid_restore_path(path):
     path = os.path.normpath(path)
-    if path.startswith('/'):
+    if path.startswith(b'/'):
         path = path[1:]
-    if '/' in path:
+    if b'/' in path:
         return True
 
 def parse_owner_mappings(type, options, fatal):
     """Traverse the options and parse all --map-TYPEs, or call Option.fatal()."""
     opt_name = '--map-' + type
-    value_rx = r'^([^=]+)=([^=]*)$'
     if type in ('uid', 'gid'):
-        value_rx = r'^(-?[0-9]+)=(-?[0-9]+)$'
+        value_rx = re.compile(br'^(-?[0-9]+)=(-?[0-9]+)$')
+    else:
+        value_rx = re.compile(br'^([^=]+)=([^=]*)$')
     owner_map = {}
     for flag in options:
         (option, parameter) = flag
         if option != opt_name:
             continue
-        match = re.match(value_rx, parameter)
+        parameter = argv_bytes(parameter)
+        match = value_rx.match(parameter)
         if not match:
-            raise fatal("couldn't parse %s as %s mapping" % (parameter, type))
+            raise fatal("couldn't parse %r as %s mapping" % (parameter, type))
         old_id, new_id = match.groups()
         if type in ('uid', 'gid'):
             old_id = int(old_id)
@@ -113,7 +119,7 @@ def hardlink_if_possible(fullname, item, top, hardlinks):
 
     target = item.meta.hardlink_target
     assert(target)
-    assert(fullname.startswith('/'))
+    assert(fullname.startswith(b'/'))
     target_versions = hardlinks.get(target)
     if target_versions:
         # Check every path in the set that we've written so far for a match.
@@ -132,13 +138,13 @@ def hardlink_if_possible(fullname, item, top, hardlinks):
     return False
 
 def write_file_content(repo, dest_path, vfs_file):
-    with vfs2.fopen(repo, vfs_file) as inf:
+    with vfs.fopen(repo, vfs_file) as inf:
         with open(dest_path, 'wb') as outf:
             for b in chunkyreader(inf):
                 outf.write(b)
 
 def write_file_content_sparsely(repo, dest_path, vfs_file):
-    with vfs2.fopen(repo, vfs_file) as inf:
+    with vfs.fopen(repo, vfs_file) as inf:
         outfd = os.open(dest_path, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o600)
         try:
             trailing_zeros = 0;
@@ -152,39 +158,39 @@ def write_file_content_sparsely(repo, dest_path, vfs_file):
 def restore(repo, parent_path, name, item, top, sparse, numeric_ids, owner_map,
             exclude_rxs, verbosity, hardlinks):
     global total_restored
-    mode = vfs2.item_mode(item)
+    mode = vfs.item_mode(item)
     treeish = S_ISDIR(mode)
-    fullname = parent_path + '/' + name
+    fullname = parent_path + b'/' + name
     # Match behavior of index --exclude-rx with respect to paths.
-    if should_rx_exclude_path(fullname + ('/' if treeish else ''),
+    if should_rx_exclude_path(fullname + (b'/' if treeish else b''),
                               exclude_rxs):
         return
 
     if not treeish:
         # Do this now so we'll have meta.symlink_target for verbose output
-        item = vfs2.augment_item_meta(repo, item, include_size=True)
+        item = vfs.augment_item_meta(repo, item, include_size=True)
         meta = item.meta
         assert(meta.mode == mode)
 
     if stat.S_ISDIR(mode):
         if verbosity >= 1:
-            print('%s/' % fullname)
+            out.write(b'%s/\n' % fullname)
     elif stat.S_ISLNK(mode):
         assert(meta.symlink_target)
         if verbosity >= 2:
-            print('%s@ -> %s' % (fullname, meta.symlink_target))
+            out.write(b'%s@ -> %s\n' % (fullname, meta.symlink_target))
     else:
         if verbosity >= 2:
-            print(fullname)
+            out.write(fullname + '\n')
 
     orig_cwd = os.getcwd()
     try:
         if treeish:
             # Assumes contents() returns '.' with the full metadata first
-            sub_items = vfs2.contents(repo, item, want_meta=True)
+            sub_items = vfs.contents(repo, item, want_meta=True)
             dot, item = next(sub_items, None)
-            assert(dot == '.')
-            item = vfs2.augment_item_meta(repo, item, include_size=True)
+            assert(dot == b'.')
+            item = vfs.augment_item_meta(repo, item, include_size=True)
             meta = item.meta
             meta.create_path(name)
             os.chdir(name)
@@ -195,7 +201,7 @@ def restore(repo, parent_path, name, item, top, sparse, numeric_ids, owner_map,
                 restore(repo, fullname, sub_name, sub_item, top, sparse,
                         numeric_ids, owner_map, exclude_rxs, verbosity,
                         hardlinks)
-            os.chdir('..')
+            os.chdir(b'..')
             apply_metadata(meta, name, numeric_ids, owner_map)
         else:
             created_hardlink = False
@@ -220,7 +226,11 @@ def restore(repo, parent_path, name, item, top, sparse, numeric_ids, owner_map,
 def main():
     o = options.Options(optspec)
     opt, flags, extra = o.parse(sys.argv[1:])
-    verbosity = opt.verbose if not opt.quiet else -1
+    verbosity = (opt.verbose or 0) if not opt.quiet else -1
+    if opt.remote:
+        opt.remote = argv_bytes(opt.remote)
+    if opt.outdir:
+        opt.outdir = argv_bytes(opt.outdir)
     
     git.check_repo_or_die()
 
@@ -237,18 +247,29 @@ def main():
         mkdirp(opt.outdir)
         os.chdir(opt.outdir)
 
-    repo = LocalRepo()
-    top = os.getcwd()
+    repo = RemoteRepo(opt.remote) if opt.remote else LocalRepo()
+    top = fsencode(os.getcwd())
     hardlinks = {}
-    for path in extra:
+    for path in [argv_bytes(x) for x in extra]:
         if not valid_restore_path(path):
             add_error("path %r doesn't include a branch and revision" % path)
             continue
         try:
-            resolved = vfs2.lresolve(repo, path, want_meta=True)
-        except vfs2.IOError as e:
+            resolved = vfs.resolve(repo, path, want_meta=True, follow=False)
+        except vfs.IOError as e:
             add_error(e)
             continue
+        if len(resolved) == 3 and resolved[2][0] == b'latest':
+            # Follow latest symlink to the actual save
+            try:
+                resolved = vfs.resolve(repo, b'latest', parent=resolved[:-1],
+                                       want_meta=True)
+            except vfs.IOError as e:
+                add_error(e)
+                continue
+            # Rename it back to 'latest'
+            resolved = tuple(elt if i != 2 else (b'latest',) + elt[1:]
+                             for i, elt in enumerate(resolved))
         path_parent, path_name = os.path.split(path)
         leaf_name, leaf_item = resolved[-1]
         if not leaf_item:
@@ -256,29 +277,29 @@ def main():
                       % ('/'.join(name for name, item in resolved),
                          path))
             continue
-        if not path_name or path_name == '.':
+        if not path_name or path_name == b'.':
             # Source is /foo/what/ever/ or /foo/what/ever/. -- extract
             # what/ever/* to the current directory, and if name == '.'
             # (i.e. /foo/what/ever/.), then also restore what/ever's
             # metadata to the current directory.
-            treeish = vfs2.item_mode(leaf_item)
+            treeish = vfs.item_mode(leaf_item)
             if not treeish:
                 add_error('%r cannot be restored as a directory' % path)
             else:
-                items = vfs2.contents(repo, leaf_item, want_meta=True)
+                items = vfs.contents(repo, leaf_item, want_meta=True)
                 dot, leaf_item = next(items, None)
-                assert(dot == '.')
+                assert dot == b'.'
                 for sub_name, sub_item in items:
-                    restore(repo, '', sub_name, sub_item, top,
+                    restore(repo, b'', sub_name, sub_item, top,
                             opt.sparse, opt.numeric_ids, owner_map,
                             exclude_rxs, verbosity, hardlinks)
-                if path_name == '.':
-                    leaf_item = vfs2.augment_item_meta(repo, leaf_item,
-                                                       include_size=True)
-                    apply_metadata(leaf_item.meta, '.',
+                if path_name == b'.':
+                    leaf_item = vfs.augment_item_meta(repo, leaf_item,
+                                                      include_size=True)
+                    apply_metadata(leaf_item.meta, b'.',
                                    opt.numeric_ids, owner_map)
         else:
-            restore(repo, '', leaf_name, leaf_item, top,
+            restore(repo, b'', leaf_name, leaf_item, top,
                     opt.sparse, opt.numeric_ids, owner_map,
                     exclude_rxs, verbosity, hardlinks)