2 import errno, sys, stat
3 from bup import options, git, metadata, vfs
4 from bup.helpers import *
7 bup restore [-C outdir] </branch/revision/path/to/dir ...>
9 C,outdir= change to given outdir before extracting files
10 numeric-ids restore numeric IDs (user, group, etc.) rather than names
11 v,verbose increase log output (can be used more than once)
12 q,quiet don't show progress meter
34 def print_info(n, fullname):
35 if stat.S_ISDIR(n.mode):
36 verbose1('%s/' % fullname)
37 elif stat.S_ISLNK(n.mode):
38 verbose2('%s@ -> %s' % (fullname, n.readlink()))
43 def create_path(n, fullname, meta):
45 meta.create_path(fullname)
47 # These fallbacks are important -- meta could be null if, for
48 # example, save created a "fake" item, i.e. a new strip/graft
49 # path element, etc. You can find cases like that by
50 # searching for "Metadata()".
52 if stat.S_ISDIR(n.mode):
54 elif stat.S_ISLNK(n.mode):
55 os.symlink(n.readlink(), fullname)
57 # Track a list of (restore_path, vfs_path, meta) triples for each path
58 # we've written for a given hardlink_target. This allows us to handle
59 # the case where we restore a set of hardlinks out of order (with
60 # respect to the original save call(s)) -- i.e. when we don't restore
61 # the hardlink_target path first. This data also allows us to attempt
62 # to handle other situations like hardlink sets that change on disk
63 # during a save, or between index and save.
66 def hardlink_compatible(target_path, target_vfs_path, target_meta,
69 if not os.path.exists(target_path):
71 target_node = top.lresolve(target_vfs_path)
72 if src_node.mode != target_node.mode \
73 or src_node.atime != target_node.atime \
74 or src_node.mtime != target_node.mtime \
75 or src_node.ctime != target_node.ctime \
76 or src_node.hash != target_node.hash:
78 if not src_meta.same_file(target_meta):
83 def hardlink_if_possible(fullname, node, meta):
84 """Find a suitable hardlink target, link to it, and return true,
85 otherwise return false."""
86 # Expect the caller to handle restoring the metadata if
87 # hardlinking isn't possible.
88 global targets_written
89 target = meta.hardlink_target
90 target_versions = targets_written.get(target)
92 # Check every path in the set that we've written so far for a match.
93 for (target_path, target_vfs_path, target_meta) in target_versions:
94 if hardlink_compatible(target_path, target_vfs_path, target_meta,
97 os.link(target_path, fullname)
100 if e.errno != errno.EXDEV:
104 targets_written[target] = target_versions
105 full_vfs_path = node.fullname()
106 target_versions.append((fullname, full_vfs_path, meta))
110 def write_file_content(fullname, n):
111 outf = open(fullname, 'wb')
113 for b in chunkyreader(n.open()):
119 def do_node(top, n, meta=None):
120 # meta will be None for dirs, and when there is no .bupm (i.e. no metadata)
121 global total_restored, opt
124 fullname = n.fullname(stop_at=top)
125 # If this is a directory, its metadata is the first entry in
126 # any .bupm file inside the directory. Get it.
127 if(stat.S_ISDIR(n.mode)):
128 mfile = n.metadata_file() # VFS file -- cannot close().
130 meta_stream = mfile.open()
131 meta = metadata.Metadata.read(meta_stream)
132 print_info(n, fullname)
134 created_hardlink = False
135 if meta and meta.hardlink_target:
136 created_hardlink = hardlink_if_possible(fullname, n, meta)
138 if not created_hardlink:
139 create_path(n, fullname, meta)
141 if stat.S_ISREG(meta.mode):
142 write_file_content(fullname, n)
143 elif stat.S_ISREG(n.mode):
144 write_file_content(fullname, n)
147 plog('Restoring: %d\r' % total_restored)
150 # Don't get metadata if this is a dir -- handled in sub do_node().
151 if meta_stream and not stat.S_ISDIR(sub.mode):
152 m = metadata.Metadata.read(meta_stream)
154 if meta and not created_hardlink:
155 meta.apply_to_path(fullname,
156 restore_numeric_ids=opt.numeric_ids)
163 o = options.Options(optspec)
164 (opt, flags, extra) = o.parse(sys.argv[1:])
166 git.check_repo_or_die()
167 top = vfs.RefList(None)
170 o.fatal('must specify at least one filename to restore')
178 path,name = os.path.split(d)
181 except vfs.NodeError, e:
184 isdir = stat.S_ISDIR(n.mode)
185 if not name or name == '.':
186 # trailing slash: extract children to cwd
188 add_error('%r: not a directory' % d)
193 # no trailing slash: extract node and its children to cwd
197 progress('Restoring: %d, done.\n' % total_restored)
200 log('WARNING: %d errors encountered while restoring.\n' % len(saved_errors))