]> arthur.barton.de Git - bup.git/blob - cmd/index-cmd.py
Change index; include atime; pack time as xstat timespec; use ns in memory.
[bup.git] / cmd / index-cmd.py
1 #!/usr/bin/env python
2 import sys, stat, time, os
3 from bup import options, git, index, drecurse, hlinkdb
4 from bup.helpers import *
5 from bup.hashsplit import GIT_MODE_TREE, GIT_MODE_FILE
6
7 class IterHelper:
8     def __init__(self, l):
9         self.i = iter(l)
10         self.cur = None
11         self.next()
12
13     def next(self):
14         try:
15             self.cur = self.i.next()
16         except StopIteration:
17             self.cur = None
18         return self.cur
19
20
21 def check_index(reader):
22     try:
23         log('check: checking forward iteration...\n')
24         e = None
25         d = {}
26         for e in reader.forward_iter():
27             if e.children_n:
28                 if opt.verbose:
29                     log('%08x+%-4d %r\n' % (e.children_ofs, e.children_n,
30                                             e.name))
31                 assert(e.children_ofs)
32                 assert(e.name.endswith('/'))
33                 assert(not d.get(e.children_ofs))
34                 d[e.children_ofs] = 1
35             if e.flags & index.IX_HASHVALID:
36                 assert(e.sha != index.EMPTY_SHA)
37                 assert(e.gitmode)
38         assert(not e or e.name == '/')  # last entry is *always* /
39         log('check: checking normal iteration...\n')
40         last = None
41         for e in reader:
42             if last:
43                 assert(last > e.name)
44             last = e.name
45     except:
46         log('index error! at %r\n' % e)
47         raise
48     log('check: passed.\n')
49
50
51 def update_index(top, excluded_paths):
52     # tmax and start must be epoch nanoseconds.
53     tmax = (time.time() - 1) * 10**9
54     ri = index.Reader(indexfile)
55     wi = index.Writer(indexfile, tmax)
56     rig = IterHelper(ri.iter(name=top))
57     tstart = int(time.time()) * 10**9
58
59     hlinks = hlinkdb.HLinkDB(indexfile + '.hlink')
60
61     hashgen = None
62     if opt.fake_valid:
63         def hashgen(name):
64             return (GIT_MODE_FILE, index.FAKE_SHA)
65
66     total = 0
67     bup_dir = os.path.abspath(git.repo())
68     for (path,pst) in drecurse.recursive_dirlist([top], xdev=opt.xdev,
69                                                  bup_dir=bup_dir,
70                                                  excluded_paths=excluded_paths):
71         if opt.verbose>=2 or (opt.verbose==1 and stat.S_ISDIR(pst.st_mode)):
72             sys.stdout.write('%s\n' % path)
73             sys.stdout.flush()
74             qprogress('Indexing: %d\r' % total)
75         elif not (total % 128):
76             qprogress('Indexing: %d\r' % total)
77         total += 1
78         while rig.cur and rig.cur.name > path:  # deleted paths
79             if rig.cur.exists():
80                 rig.cur.set_deleted()
81                 rig.cur.repack()
82                 if rig.cur.nlink > 1 and not stat.S_ISDIR(rig.cur.mode):
83                     hlinks.del_path(rig.cur.name)
84             rig.next()
85         if rig.cur and rig.cur.name == path:    # paths that already existed
86             if not stat.S_ISDIR(rig.cur.mode) and rig.cur.nlink > 1:
87                 hlinks.del_path(rig.cur.name)
88             if not stat.S_ISDIR(pst.st_mode) and pst.st_nlink > 1:
89                 hlinks.add_path(path, pst.st_dev, pst.st_ino)
90             rig.cur.from_stat(pst, tstart)
91             if not (rig.cur.flags & index.IX_HASHVALID):
92                 if hashgen:
93                     (rig.cur.gitmode, rig.cur.sha) = hashgen(path)
94                     rig.cur.flags |= index.IX_HASHVALID
95             if opt.fake_invalid:
96                 rig.cur.invalidate()
97             rig.cur.repack()
98             rig.next()
99         else:  # new paths
100             wi.add(path, pst, hashgen = hashgen)
101             if not stat.S_ISDIR(pst.st_mode) and pst.st_nlink > 1:
102                 hlinks.add_path(path, pst.st_dev, pst.st_ino)
103
104     progress('Indexing: %d, done.\n' % total)
105     
106     hlinks.prepare_save()
107
108     if ri.exists():
109         ri.save()
110         wi.flush()
111         if wi.count:
112             wr = wi.new_reader()
113             if opt.check:
114                 log('check: before merging: oldfile\n')
115                 check_index(ri)
116                 log('check: before merging: newfile\n')
117                 check_index(wr)
118             mi = index.Writer(indexfile, tmax)
119
120             for e in index.merge(ri, wr):
121                 # FIXME: shouldn't we remove deleted entries eventually?  When?
122                 mi.add_ixentry(e)
123
124             ri.close()
125             mi.close()
126             wr.close()
127         wi.abort()
128     else:
129         wi.close()
130
131     hlinks.commit_save()
132
133
134 optspec = """
135 bup index <-p|m|s|u> [options...] <filenames...>
136 --
137  Modes:
138 p,print    print the index entries for the given names (also works with -u)
139 m,modified print only added/deleted/modified files (implies -p)
140 s,status   print each filename with a status char (A/M/D) (implies -p)
141 u,update   recursively update the index entries for the given file/dir names (default if no mode is specified)
142 check      carefully check index file integrity
143  Options:
144 H,hash     print the hash for each object next to its name
145 l,long     print more information about each file
146 fake-valid mark all index entries as up-to-date even if they aren't
147 fake-invalid mark all index entries as invalid
148 f,indexfile=  the name of the index file (normally BUP_DIR/bupindex)
149 exclude=   a path to exclude from the backup (can be used more than once)
150 exclude-from= a file that contains exclude paths (can be used more than once)
151 v,verbose  increase log output (can be used more than once)
152 x,xdev,one-file-system  don't cross filesystem boundaries
153 """
154 o = options.Options(optspec)
155 (opt, flags, extra) = o.parse(sys.argv[1:])
156
157 if not (opt.modified or opt['print'] or opt.status or opt.update or opt.check):
158     opt.update = 1
159 if (opt.fake_valid or opt.fake_invalid) and not opt.update:
160     o.fatal('--fake-{in,}valid are meaningless without -u')
161 if opt.fake_valid and opt.fake_invalid:
162     o.fatal('--fake-valid is incompatible with --fake-invalid')
163
164 # FIXME: remove this once we account for timestamp races, i.e. index;
165 # touch new-file; index.  It's possible for this to happen quickly
166 # enough that new-file ends up with the same timestamp as the first
167 # index, and then bup will ignore it.
168 tick_start = time.time()
169 time.sleep(1 - (tick_start - int(tick_start)))
170
171 git.check_repo_or_die()
172 indexfile = opt.indexfile or git.repo('bupindex')
173
174 handle_ctrl_c()
175
176 if opt.check:
177     log('check: starting initial check.\n')
178     check_index(index.Reader(indexfile))
179
180 excluded_paths = drecurse.parse_excludes(flags)
181
182 paths = index.reduce_paths(extra)
183
184 if opt.update:
185     if not extra:
186         o.fatal('update mode (-u) requested but no paths given')
187     for (rp,path) in paths:
188         update_index(rp, excluded_paths)
189
190 if opt['print'] or opt.status or opt.modified:
191     for (name, ent) in index.Reader(indexfile).filter(extra or ['']):
192         if (opt.modified 
193             and (ent.is_valid() or ent.is_deleted() or not ent.mode)):
194             continue
195         line = ''
196         if opt.status:
197             if ent.is_deleted():
198                 line += 'D '
199             elif not ent.is_valid():
200                 if ent.sha == index.EMPTY_SHA:
201                     line += 'A '
202                 else:
203                     line += 'M '
204             else:
205                 line += '  '
206         if opt.hash:
207             line += ent.sha.encode('hex') + ' '
208         if opt.long:
209             line += "%7s %7s " % (oct(ent.mode), oct(ent.gitmode))
210         print line + (name or './')
211
212 if opt.check and (opt['print'] or opt.status or opt.modified or opt.update):
213     log('check: starting final check.\n')
214     check_index(index.Reader(indexfile))
215
216 if saved_errors:
217     log('WARNING: %d errors encountered.\n' % len(saved_errors))
218     sys.exit(1)