3 bup_python="$(dirname "$0")/bup-python" || exit $?
4 exec "$bup_python" "$0" ${1+"$@"}
8 import sys, stat, time, os, errno, re
10 from bup import metadata, options, git, index, drecurse, hlinkdb
11 from bup.hashsplit import GIT_MODE_TREE, GIT_MODE_FILE
12 from bup.helpers import (handle_ctrl_c, log, parse_excludes, parse_rx_excludes,
13 progress, qprogress, saved_errors)
17 def __init__(self, l):
24 self.cur = self.i.next()
30 def check_index(reader):
32 log('check: checking forward iteration...\n')
35 for e in reader.forward_iter():
38 log('%08x+%-4d %r\n' % (e.children_ofs, e.children_n,
40 assert(e.children_ofs)
41 assert(e.name.endswith('/'))
42 assert(not d.get(e.children_ofs))
44 if e.flags & index.IX_HASHVALID:
45 assert(e.sha != index.EMPTY_SHA)
47 assert(not e or e.name == '/') # last entry is *always* /
48 log('check: checking normal iteration...\n')
55 log('index error! at %r\n' % e)
57 log('check: passed.\n')
60 def clear_index(indexfile):
61 indexfiles = [indexfile, indexfile + '.meta', indexfile + '.hlink']
62 for indexfile in indexfiles:
63 path = git.repo(indexfile)
67 log('clear: removed %s\n' % path)
69 if e.errno != errno.ENOENT:
73 def update_index(top, excluded_paths, exclude_rxs):
74 # tmax and start must be epoch nanoseconds.
75 tmax = (time.time() - 1) * 10**9
76 ri = index.Reader(indexfile)
77 msw = index.MetaStoreWriter(indexfile + '.meta')
78 wi = index.Writer(indexfile, msw, tmax)
79 rig = IterHelper(ri.iter(name=top))
80 tstart = int(time.time()) * 10**9
82 hlinks = hlinkdb.HLinkDB(indexfile + '.hlink')
87 return (GIT_MODE_FILE, index.FAKE_SHA)
90 bup_dir = os.path.abspath(git.repo())
91 index_start = time.time()
92 for (path,pst) in drecurse.recursive_dirlist([top], xdev=opt.xdev,
94 excluded_paths=excluded_paths,
95 exclude_rxs=exclude_rxs):
96 if opt.verbose>=2 or (opt.verbose==1 and stat.S_ISDIR(pst.st_mode)):
97 sys.stdout.write('%s\n' % path)
99 elapsed = time.time() - index_start
100 paths_per_sec = total / elapsed if elapsed else 0
101 qprogress('Indexing: %d (%d paths/s)\r' % (total, paths_per_sec))
102 elif not (total % 128):
103 elapsed = time.time() - index_start
104 paths_per_sec = total / elapsed if elapsed else 0
105 qprogress('Indexing: %d (%d paths/s)\r' % (total, paths_per_sec))
107 while rig.cur and rig.cur.name > path: # deleted paths
109 rig.cur.set_deleted()
111 if rig.cur.nlink > 1 and not stat.S_ISDIR(rig.cur.mode):
112 hlinks.del_path(rig.cur.name)
114 if rig.cur and rig.cur.name == path: # paths that already existed
116 meta = metadata.from_path(path, statinfo=pst)
117 except (OSError, IOError) as e:
121 if not stat.S_ISDIR(rig.cur.mode) and rig.cur.nlink > 1:
122 hlinks.del_path(rig.cur.name)
123 if not stat.S_ISDIR(pst.st_mode) and pst.st_nlink > 1:
124 hlinks.add_path(path, pst.st_dev, pst.st_ino)
125 # Clear these so they don't bloat the store -- they're
126 # already in the index (since they vary a lot and they're
127 # fixed length). If you've noticed "tmax", you might
128 # wonder why it's OK to do this, since that code may
129 # adjust (mangle) the index mtime and ctime -- producing
130 # fake values which must not end up in a .bupm. However,
131 # it looks like that shouldn't be possible: (1) When
132 # "save" validates the index entry, it always reads the
133 # metadata from the filesytem. (2) Metadata is only
134 # read/used from the index if hashvalid is true. (3) index
135 # always invalidates "faked" entries, because "old != new"
137 meta.ctime = meta.mtime = meta.atime = 0
138 meta_ofs = msw.store(meta)
139 rig.cur.from_stat(pst, meta_ofs, tstart,
140 check_device=opt.check_device)
141 if not (rig.cur.flags & index.IX_HASHVALID):
143 (rig.cur.gitmode, rig.cur.sha) = hashgen(path)
144 rig.cur.flags |= index.IX_HASHVALID
151 meta = metadata.from_path(path, statinfo=pst)
152 except (OSError, IOError) as e:
155 # See same assignment to 0, above, for rationale.
156 meta.atime = meta.mtime = meta.ctime = 0
157 meta_ofs = msw.store(meta)
158 wi.add(path, pst, meta_ofs, hashgen = hashgen)
159 if not stat.S_ISDIR(pst.st_mode) and pst.st_nlink > 1:
160 hlinks.add_path(path, pst.st_dev, pst.st_ino)
162 elapsed = time.time() - index_start
163 paths_per_sec = total / elapsed if elapsed else 0
164 progress('Indexing: %d, done (%d paths/s).\n' % (total, paths_per_sec))
166 hlinks.prepare_save()
174 log('check: before merging: oldfile\n')
176 log('check: before merging: newfile\n')
178 mi = index.Writer(indexfile, msw, tmax)
180 for e in index.merge(ri, wr):
181 # FIXME: shouldn't we remove deleted entries eventually? When?
196 bup index <-p|-m|-s|-u|--clear|--check> [options...] <filenames...>
199 p,print print the index entries for the given names (also works with -u)
200 m,modified print only added/deleted/modified files (implies -p)
201 s,status print each filename with a status char (A/M/D) (implies -p)
202 u,update recursively update the index entries for the given file/dir names (default if no mode is specified)
203 check carefully check index file integrity
204 clear clear the default index
206 H,hash print the hash for each object next to its name
207 l,long print more information about each file
208 no-check-device don't invalidate an entry if the containing device changes
209 fake-valid mark all index entries as up-to-date even if they aren't
210 fake-invalid mark all index entries as invalid
211 f,indexfile= the name of the index file (normally BUP_DIR/bupindex)
212 exclude= a path to exclude from the backup (may be repeated)
213 exclude-from= skip --exclude paths in file (may be repeated)
214 exclude-rx= skip paths matching the unanchored regex (may be repeated)
215 exclude-rx-from= skip --exclude-rx patterns in file (may be repeated)
216 v,verbose increase log output (can be used more than once)
217 x,xdev,one-file-system don't cross filesystem boundaries
219 o = options.Options(optspec)
220 (opt, flags, extra) = o.parse(sys.argv[1:])
222 if not (opt.modified or \
229 if (opt.fake_valid or opt.fake_invalid) and not opt.update:
230 o.fatal('--fake-{in,}valid are meaningless without -u')
231 if opt.fake_valid and opt.fake_invalid:
232 o.fatal('--fake-valid is incompatible with --fake-invalid')
233 if opt.clear and opt.indexfile:
234 o.fatal('cannot clear an external index (via -f)')
236 # FIXME: remove this once we account for timestamp races, i.e. index;
237 # touch new-file; index. It's possible for this to happen quickly
238 # enough that new-file ends up with the same timestamp as the first
239 # index, and then bup will ignore it.
240 tick_start = time.time()
241 time.sleep(1 - (tick_start - int(tick_start)))
243 git.check_repo_or_die()
244 indexfile = opt.indexfile or git.repo('bupindex')
249 log('check: starting initial check.\n')
250 check_index(index.Reader(indexfile))
253 log('clear: clearing index.\n')
254 clear_index(indexfile)
256 excluded_paths = parse_excludes(flags, o.fatal)
257 exclude_rxs = parse_rx_excludes(flags, o.fatal)
258 paths = index.reduce_paths(extra)
262 o.fatal('update mode (-u) requested but no paths given')
263 for (rp,path) in paths:
264 update_index(rp, excluded_paths, exclude_rxs)
266 if opt['print'] or opt.status or opt.modified:
267 for (name, ent) in index.Reader(indexfile).filter(extra or ['']):
269 and (ent.is_valid() or ent.is_deleted() or not ent.mode)):
275 elif not ent.is_valid():
276 if ent.sha == index.EMPTY_SHA:
283 line += ent.sha.encode('hex') + ' '
285 line += "%7s %7s " % (oct(ent.mode), oct(ent.gitmode))
286 print line + (name or './')
288 if opt.check and (opt['print'] or opt.status or opt.modified or opt.update):
289 log('check: starting final check.\n')
290 check_index(index.Reader(indexfile))
293 log('WARNING: %d errors encountered.\n' % len(saved_errors))