]> arthur.barton.de Git - bup.git/blob - cmd-index.py
cmd-save: completely reimplement using the indexfile.
[bup.git] / cmd-index.py
1 #!/usr/bin/env python2.5
2 import os, sys, stat
3 import options, git, index
4 from helpers import *
5
6 class OsFile:
7     def __init__(self, path):
8         self.fd = None
9         self.fd = os.open(path, os.O_RDONLY|os.O_LARGEFILE|os.O_NOFOLLOW)
10         #self.st = os.fstat(self.fd)
11         
12     def __del__(self):
13         if self.fd:
14             fd = self.fd
15             self.fd = None
16             os.close(fd)
17
18     def fchdir(self):
19         os.fchdir(self.fd)
20
21
22 saved_errors = []
23 def add_error(e):
24     saved_errors.append(e)
25     log('\n%s\n' % e)
26
27
28 # the use of fchdir() and lstat() are for two reasons:
29 #  - help out the kernel by not making it repeatedly look up the absolute path
30 #  - avoid race conditions caused by doing listdir() on a changing symlink
31 def handle_path(ri, wi, dir, name, pst, xdev, can_delete_siblings):
32     hashgen = None
33     if opt.fake_valid:
34         def hashgen(name):
35             return (0, index.FAKE_SHA)
36     
37     dirty = 0
38     path = dir + name
39     #log('handle_path(%r,%r)\n' % (dir, name))
40     if stat.S_ISDIR(pst.st_mode):
41         if opt.verbose == 1: # log dirs only
42             sys.stdout.write('%s\n' % path)
43             sys.stdout.flush()
44         try:
45             OsFile(name).fchdir()
46         except OSError, e:
47             add_error(Exception('in %s: %s' % (dir, str(e))))
48             return 0
49         try:
50             try:
51                 ld = os.listdir('.')
52                 #log('* %r: %r\n' % (name, ld))
53             except OSError, e:
54                 add_error(Exception('in %s: %s' % (path, str(e))))
55                 return 0
56             lds = []
57             for p in ld:
58                 try:
59                     st = os.lstat(p)
60                 except OSError, e:
61                     add_error(Exception('in %s: %s' % (path, str(e))))
62                     continue
63                 if xdev != None and st.st_dev != xdev:
64                     log('Skipping %r: different filesystem.\n' 
65                         % index.realpath(p))
66                     continue
67                 if stat.S_ISDIR(st.st_mode):
68                     p = slashappend(p)
69                 lds.append((p, st))
70             for p,st in reversed(sorted(lds)):
71                 dirty += handle_path(ri, wi, path, p, st, xdev,
72                                      can_delete_siblings = True)
73         finally:
74             os.chdir('..')
75     #log('endloop: ri.cur:%r path:%r\n' % (ri.cur.name, path))
76     while ri.cur and ri.cur.name > path:
77         #log('ricur:%r path:%r\n' % (ri.cur, path))
78         if can_delete_siblings and dir and ri.cur.name.startswith(dir):
79             #log('    --- deleting\n')
80             ri.cur.flags &= ~(index.IX_EXISTS | index.IX_HASHVALID)
81             ri.cur.repack()
82             dirty += 1
83         ri.next()
84     if ri.cur and ri.cur.name == path:
85         dirty += ri.cur.from_stat(pst)
86         if dirty or not (ri.cur.flags & index.IX_HASHVALID):
87             #log('   --- updating %r\n' % path)
88             if hashgen:
89                 (ri.cur.gitmode, ri.cur.sha) = hashgen(name)
90                 ri.cur.flags |= index.IX_HASHVALID
91             ri.cur.repack()
92         ri.next()
93     else:
94         wi.add(path, pst, hashgen = hashgen)
95         dirty += 1
96     if opt.verbose > 1:  # all files, not just dirs
97         sys.stdout.write('%s\n' % path)
98         sys.stdout.flush()
99     return dirty
100
101
102 def merge_indexes(out, r1, r2):
103     log('bup: merging indexes.\n')
104     for e in index._last_writer_wins_iter([r1, r2]):
105         #if e.flags & index.IX_EXISTS:
106             out.add_ixentry(e)
107
108
109 class MergeGetter:
110     def __init__(self, l):
111         self.i = iter(l)
112         self.cur = None
113         self.next()
114
115     def next(self):
116         try:
117             self.cur = self.i.next()
118         except StopIteration:
119             self.cur = None
120         return self.cur
121
122
123 def update_index(path):
124     ri = index.Reader(indexfile)
125     wi = index.Writer(indexfile)
126     rig = MergeGetter(ri)
127     
128     rpath = index.realpath(path)
129     st = os.lstat(rpath)
130     if opt.xdev:
131         xdev = st.st_dev
132     else:
133         xdev = None
134     f = OsFile('.')
135     if rpath[-1] == '/':
136         rpath = rpath[:-1]
137     (dir, name) = os.path.split(rpath)
138     dir = slashappend(dir)
139     if stat.S_ISDIR(st.st_mode) and (not rpath or rpath[-1] != '/'):
140         name += '/'
141         can_delete_siblings = True
142     else:
143         can_delete_siblings = False
144     OsFile(dir or '/').fchdir()
145     dirty = handle_path(rig, wi, dir, name, st, xdev, can_delete_siblings)
146
147     # make sure all the parents of the updated path exist and are invalidated
148     # if appropriate.
149     while 1:
150         (rpath, junk) = os.path.split(rpath)
151         if not rpath:
152             break
153         elif rpath == '/':
154             p = rpath
155         else:
156             p = rpath + '/'
157         while rig.cur and rig.cur.name > p:
158             #log('FINISHING: %r path=%r d=%r\n' % (rig.cur.name, p, dirty))
159             rig.next()
160         if rig.cur and rig.cur.name == p:
161             if dirty:
162                 rig.cur.flags &= ~index.IX_HASHVALID
163                 rig.cur.repack()
164         else:
165             wi.add(p, os.lstat(p))
166         if p == '/':
167             break
168     
169     f.fchdir()
170     ri.save()
171     if wi.count:
172         mi = index.Writer(indexfile)
173         merge_indexes(mi, ri, wi.new_reader())
174         mi.close()
175     wi.abort()
176
177
178 optspec = """
179 bup index <-p|s|m|u> [options...] <filenames...>
180 --
181 p,print    print the index entries for the given names (also works with -u)
182 m,modified print only added/deleted/modified files (implies -p)
183 s,status   print each filename with a status char (A/M/D) (implies -p)
184 H,hash     print the hash for each object next to its name (implies -p)
185 u,update   (recursively) update the index entries for the given filenames
186 x,xdev,one-file-system  don't cross filesystem boundaries
187 fake-valid    mark all index entries as up-to-date even if they aren't
188 f,indexfile=  the name of the index file (default 'index')
189 v,verbose  increase log output (can be used more than once)
190 """
191 o = options.Options('bup index', optspec)
192 (opt, flags, extra) = o.parse(sys.argv[1:])
193
194 if not (opt.modified or opt['print'] or opt.status or opt.update):
195     log('bup index: you must supply one or more of -p, -s, -m, or -u\n')
196     o.usage()
197 if opt.fake_valid and not opt.update:
198     log('bup index: --fake-valid is meaningless without -u\n')
199     o.usage()
200
201 git.check_repo_or_die()
202 indexfile = opt.indexfile or git.repo('bupindex')
203
204 paths = index.reduce_paths(extra)
205
206 if opt.update:
207     if not paths:
208         log('bup index: update (-u) requested but no paths given\n')
209         o.usage()
210     for (rp, path) in paths:
211         update_index(rp)
212
213 if opt['print'] or opt.status or opt.modified:
214     for (name, ent) in index.Reader(indexfile).filter(extra or ['']):
215         if opt.modified and ent.flags & index.IX_HASHVALID:
216             continue
217         line = ''
218         if opt.status:
219             if not ent.flags & index.IX_EXISTS:
220                 line += 'D '
221             elif not ent.flags & index.IX_HASHVALID:
222                 if ent.sha == index.EMPTY_SHA:
223                     line += 'A '
224                 else:
225                     line += 'M '
226             else:
227                 line += '  '
228         if opt.hash:
229             line += ent.sha.encode('hex') + ' '
230         print line + (name or './')
231         #print repr(ent)
232
233 if saved_errors:
234     log('WARNING: %d errors encountered.\n' % len(saved_errors))
235     exit(1)