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