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