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