3 import sys, stat, time, os, errno, re
4 from bup import metadata, options, git, index, drecurse, hlinkdb
5 from bup.helpers import *
6 from bup.hashsplit import GIT_MODE_TREE, GIT_MODE_FILE
16 self.cur = self.i.next()
22 def check_index(reader):
24 log('check: checking forward iteration...\n')
27 for e in reader.forward_iter():
30 log('%08x+%-4d %r\n' % (e.children_ofs, e.children_n,
32 assert(e.children_ofs)
33 assert(e.name.endswith('/'))
34 assert(not d.get(e.children_ofs))
36 if e.flags & index.IX_HASHVALID:
37 assert(e.sha != index.EMPTY_SHA)
39 assert(not e or e.name == '/') # last entry is *always* /
40 log('check: checking normal iteration...\n')
47 log('index error! at %r\n' % e)
49 log('check: passed.\n')
52 def clear_index(indexfile):
53 indexfiles = [indexfile, indexfile + '.meta', indexfile + '.hlink']
54 for indexfile in indexfiles:
55 path = git.repo(indexfile)
59 log('clear: removed %s\n' % path)
61 if e.errno != errno.ENOENT:
65 def update_index(top, excluded_paths, exclude_rxs):
66 # tmax and start must be epoch nanoseconds.
67 tmax = (time.time() - 1) * 10**9
68 ri = index.Reader(indexfile)
69 msw = index.MetaStoreWriter(indexfile + '.meta')
70 wi = index.Writer(indexfile, msw, tmax)
71 rig = IterHelper(ri.iter(name=top))
72 tstart = int(time.time()) * 10**9
74 hlinks = hlinkdb.HLinkDB(indexfile + '.hlink')
79 return (GIT_MODE_FILE, index.FAKE_SHA)
82 bup_dir = os.path.abspath(git.repo())
83 for (path,pst) in drecurse.recursive_dirlist([top], xdev=opt.xdev,
85 excluded_paths=excluded_paths,
86 exclude_rxs=exclude_rxs):
87 if opt.verbose>=2 or (opt.verbose==1 and stat.S_ISDIR(pst.st_mode)):
88 sys.stdout.write('%s\n' % path)
90 qprogress('Indexing: %d\r' % total)
91 elif not (total % 128):
92 qprogress('Indexing: %d\r' % total)
94 while rig.cur and rig.cur.name > path: # deleted paths
98 if rig.cur.nlink > 1 and not stat.S_ISDIR(rig.cur.mode):
99 hlinks.del_path(rig.cur.name)
101 if rig.cur and rig.cur.name == path: # paths that already existed
103 meta = metadata.from_path(path, statinfo=pst)
104 except (OSError, IOError), e:
108 if not stat.S_ISDIR(rig.cur.mode) and rig.cur.nlink > 1:
109 hlinks.del_path(rig.cur.name)
110 if not stat.S_ISDIR(pst.st_mode) and pst.st_nlink > 1:
111 hlinks.add_path(path, pst.st_dev, pst.st_ino)
112 # Clear these so they don't bloat the store -- they're
113 # already in the index (since they vary a lot and they're
114 # fixed length). If you've noticed "tmax", you might
115 # wonder why it's OK to do this, since that code may
116 # adjust (mangle) the index mtime and ctime -- producing
117 # fake values which must not end up in a .bupm. However,
118 # it looks like that shouldn't be possible: (1) When
119 # "save" validates the index entry, it always reads the
120 # metadata from the filesytem. (2) Metadata is only
121 # read/used from the index if hashvalid is true. (3) index
122 # always invalidates "faked" entries, because "old != new"
124 meta.ctime = meta.mtime = meta.atime = 0
125 meta_ofs = msw.store(meta)
126 rig.cur.from_stat(pst, meta_ofs, tstart,
127 check_device=opt.check_device)
128 if not (rig.cur.flags & index.IX_HASHVALID):
130 (rig.cur.gitmode, rig.cur.sha) = hashgen(path)
131 rig.cur.flags |= index.IX_HASHVALID
138 meta = metadata.from_path(path, statinfo=pst)
139 except (OSError, IOError), e:
142 # See same assignment to 0, above, for rationale.
143 meta.atime = meta.mtime = meta.ctime = 0
144 meta_ofs = msw.store(meta)
145 wi.add(path, pst, meta_ofs, hashgen = hashgen)
146 if not stat.S_ISDIR(pst.st_mode) and pst.st_nlink > 1:
147 hlinks.add_path(path, pst.st_dev, pst.st_ino)
149 progress('Indexing: %d, done.\n' % total)
151 hlinks.prepare_save()
159 log('check: before merging: oldfile\n')
161 log('check: before merging: newfile\n')
163 mi = index.Writer(indexfile, msw, tmax)
165 for e in index.merge(ri, wr):
166 # FIXME: shouldn't we remove deleted entries eventually? When?
181 bup index <-p|m|s|u> [options...] <filenames...>
184 p,print print the index entries for the given names (also works with -u)
185 m,modified print only added/deleted/modified files (implies -p)
186 s,status print each filename with a status char (A/M/D) (implies -p)
187 u,update recursively update the index entries for the given file/dir names (default if no mode is specified)
188 check carefully check index file integrity
189 clear clear the default index
191 H,hash print the hash for each object next to its name
192 l,long print more information about each file
193 no-check-device don't invalidate an entry if the containing device changes
194 fake-valid mark all index entries as up-to-date even if they aren't
195 fake-invalid mark all index entries as invalid
196 f,indexfile= the name of the index file (normally BUP_DIR/bupindex)
197 exclude= a path to exclude from the backup (may be repeated)
198 exclude-from= skip --exclude paths in file (may be repeated)
199 exclude-rx= skip paths matching the unanchored regex (may be repeated)
200 exclude-rx-from= skip --exclude-rx patterns in file (may be repeated)
201 v,verbose increase log output (can be used more than once)
202 x,xdev,one-file-system don't cross filesystem boundaries
204 o = options.Options(optspec)
205 (opt, flags, extra) = o.parse(sys.argv[1:])
207 if not (opt.modified or \
214 if (opt.fake_valid or opt.fake_invalid) and not opt.update:
215 o.fatal('--fake-{in,}valid are meaningless without -u')
216 if opt.fake_valid and opt.fake_invalid:
217 o.fatal('--fake-valid is incompatible with --fake-invalid')
218 if opt.clear and opt.indexfile:
219 o.fatal('cannot clear an external index (via -f)')
221 # FIXME: remove this once we account for timestamp races, i.e. index;
222 # touch new-file; index. It's possible for this to happen quickly
223 # enough that new-file ends up with the same timestamp as the first
224 # index, and then bup will ignore it.
225 tick_start = time.time()
226 time.sleep(1 - (tick_start - int(tick_start)))
228 git.check_repo_or_die()
229 indexfile = opt.indexfile or git.repo('bupindex')
234 log('check: starting initial check.\n')
235 check_index(index.Reader(indexfile))
238 log('clear: clearing index.\n')
239 clear_index(indexfile)
241 excluded_paths = parse_excludes(flags, o.fatal)
242 exclude_rxs = parse_rx_excludes(flags, o.fatal)
243 paths = index.reduce_paths(extra)
247 o.fatal('update mode (-u) requested but no paths given')
248 for (rp,path) in paths:
249 update_index(rp, excluded_paths, exclude_rxs)
251 if opt['print'] or opt.status or opt.modified:
252 for (name, ent) in index.Reader(indexfile).filter(extra or ['']):
254 and (ent.is_valid() or ent.is_deleted() or not ent.mode)):
260 elif not ent.is_valid():
261 if ent.sha == index.EMPTY_SHA:
268 line += ent.sha.encode('hex') + ' '
270 line += "%7s %7s " % (oct(ent.mode), oct(ent.gitmode))
271 print line + (name or './')
273 if opt.check and (opt['print'] or opt.status or opt.modified or opt.update):
274 log('check: starting final check.\n')
275 check_index(index.Reader(indexfile))
278 log('WARNING: %d errors encountered.\n' % len(saved_errors))