]> arthur.barton.de Git - bup.git/blobdiff - cmd/restore-cmd.py
Use next(it), not it.next() and drop the helpers fallback
[bup.git] / cmd / restore-cmd.py
index 158a707a8532210fb7246b6a1ad6de67816c852a..9fbaf909ee14eeac5553e0b9f648f303dfecd819 100755 (executable)
@@ -1,7 +1,18 @@
-#!/usr/bin/env python
-import copy, errno, sys, stat, re
+#!/bin/sh
+"""": # -*-python-*-
+bup_python="$(dirname "$0")/bup-python" || exit $?
+exec "$bup_python" "$0" ${1+"$@"}
+"""
+# end of bup preamble
+
+import copy, errno, os, sys, stat, re
+
 from bup import options, git, metadata, vfs
-from bup.helpers import *
+from bup._helpers import write_sparsely
+from bup.helpers import (add_error, chunkyreader, handle_ctrl_c, log, mkdirp,
+                         parse_rx_excludes, progress, qprogress, saved_errors,
+                         should_rx_exclude_path, unlink)
+
 
 optspec = """
 bup restore [-C outdir] </branch/revision/path/to/dir ...>
@@ -10,6 +21,7 @@ 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)
 exclude-rx-from= skip --exclude-rx patterns in file (may be repeated)
+sparse      create sparse files
 v,verbose   increase log output (can be used more than once)
 map-user=   given OLD=NEW, restore OLD user as NEW user
 map-group=  given OLD=NEW, restore OLD group as NEW group
@@ -20,6 +32,9 @@ q,quiet     don't show progress meter
 
 total_restored = 0
 
+# stdout should be flushed after each line, even when not connected to a tty
+sys.stdout.flush()
+sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 1)
 
 def verbose1(s):
     if opt.verbose >= 1:
@@ -141,7 +156,7 @@ def hardlink_if_possible(fullname, node, meta):
                 try:
                     os.link(target_path, fullname)
                     return True
-                except OSError, e:
+                except OSError as e:
                     if e.errno != errno.EXDEV:
                         raise
     else:
@@ -161,6 +176,18 @@ def write_file_content(fullname, n):
         outf.close()
 
 
+def write_file_content_sparsely(fullname, n):
+    outfd = os.open(fullname, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o600)
+    try:
+        trailing_zeros = 0;
+        for b in chunkyreader(n.open()):
+            trailing_zeros = write_sparsely(outfd, b, 512, trailing_zeros)
+        pos = os.lseek(outfd, trailing_zeros, os.SEEK_END)
+        os.ftruncate(outfd, pos)
+    finally:
+        os.close(outfd)
+
+
 def find_dir_item_metadata_by_name(dir, name):
     """Find metadata in dir (a node) for an item with the given name,
     or for the directory itself if the name is ''."""
@@ -185,7 +212,7 @@ def find_dir_item_metadata_by_name(dir, name):
             meta_stream.close()
 
 
-def do_root(n, owner_map, restore_root_meta = True):
+def do_root(n, sparse, owner_map, restore_root_meta = True):
     # Very similar to do_node(), except that this function doesn't
     # create a path for n's destination directory (and so ignores
     # n.fullname).  It assumes the destination is '.', and restores
@@ -208,21 +235,21 @@ def do_root(n, owner_map, restore_root_meta = True):
             # Don't get metadata if this is a dir -- handled in sub do_node().
             if meta_stream and not stat.S_ISDIR(sub.mode):
                 m = metadata.Metadata.read(meta_stream)
-            do_node(n, sub, owner_map, meta = m)
+            do_node(n, sub, sparse, owner_map, meta = m)
         if root_meta and restore_root_meta:
             apply_metadata(root_meta, '.', opt.numeric_ids, owner_map)
     finally:
         if meta_stream:
             meta_stream.close()
 
-
-def do_node(top, n, owner_map, meta = None):
+def do_node(top, n, sparse, owner_map, meta = None):
     # Create n.fullname(), relative to the current directory, and
     # restore all of its metadata, when available.  The meta argument
     # will be None for dirs, or when there is no .bupm (i.e. no
     # metadata).
     global total_restored, opt
     meta_stream = None
+    write_content = sparse and write_file_content_sparsely or write_file_content
     try:
         fullname = n.fullname(stop_at=top)
         # Match behavior of index --exclude-rx with respect to paths.
@@ -248,9 +275,9 @@ def do_node(top, n, owner_map, meta = None):
             create_path(n, fullname, meta)
             if meta:
                 if stat.S_ISREG(meta.mode):
-                    write_file_content(fullname, n)
+                    write_content(fullname, n)
             elif stat.S_ISREG(n.mode):
-                write_file_content(fullname, n)
+                write_content(fullname, n)
 
         total_restored += 1
         plog('Restoring: %d\r' % total_restored)
@@ -259,12 +286,13 @@ def do_node(top, n, owner_map, meta = None):
             # Don't get metadata if this is a dir -- handled in sub do_node().
             if meta_stream and not stat.S_ISDIR(sub.mode):
                 m = metadata.Metadata.read(meta_stream)
-            do_node(top, sub, owner_map, meta = m)
+            do_node(top, sub, sparse, owner_map, meta = m)
         if meta and not created_hardlink:
             apply_metadata(meta, fullname, opt.numeric_ids, owner_map)
     finally:
         if meta_stream:
             meta_stream.close()
+        n.release()
 
 
 handle_ctrl_c()
@@ -296,7 +324,7 @@ for d in extra:
     path,name = os.path.split(d)
     try:
         n = top.lresolve(d)
-    except vfs.NodeError, e:
+    except vfs.NodeError as e:
         add_error(e)
         continue
     isdir = stat.S_ISDIR(n.mode)
@@ -308,7 +336,7 @@ for d in extra:
         if not isdir:
             add_error('%r: not a directory' % d)
         else:
-            do_root(n, owner_map, restore_root_meta = (name == '.'))
+            do_root(n, opt.sparse, owner_map, restore_root_meta = (name == '.'))
     else:
         # Source is /foo/what/ever -- extract ./ever to cwd.
         if isinstance(n, vfs.FakeSymlink):
@@ -319,10 +347,10 @@ for d in extra:
             target = n.dereference()
             mkdirp(n.name)
             os.chdir(n.name)
-            do_root(target, owner_map)
+            do_root(target, opt.sparse, owner_map)
         else: # Not a directory or fake symlink.
             meta = find_dir_item_metadata_by_name(n.parent, n.name)
-            do_node(n.parent, n, owner_map, meta = meta)
+            do_node(n.parent, n, opt.sparse, owner_map, meta = meta)
 
 if not opt.quiet:
     progress('Restoring: %d, done.\n' % total_restored)