##// END OF EJS Templates
dirstate: minor status cleanups
Matt Mackall -
r6591:eda3fd32 default
parent child Browse files
Show More
@@ -1,640 +1,639 b''
1 """
1 """
2 dirstate.py - working directory tracking for mercurial
2 dirstate.py - working directory tracking for mercurial
3
3
4 Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
5
5
6 This software may be used and distributed according to the terms
6 This software may be used and distributed according to the terms
7 of the GNU General Public License, incorporated herein by reference.
7 of the GNU General Public License, incorporated herein by reference.
8 """
8 """
9
9
10 from node import nullid
10 from node import nullid
11 from i18n import _
11 from i18n import _
12 import struct, os, bisect, stat, strutil, util, errno, ignore
12 import struct, os, bisect, stat, strutil, util, errno, ignore
13 import cStringIO, osutil, sys
13 import cStringIO, osutil, sys
14
14
15 _unknown = ('?', 0, 0, 0)
15 _unknown = ('?', 0, 0, 0)
16 _format = ">cllll"
16 _format = ">cllll"
17
17
18 class dirstate(object):
18 class dirstate(object):
19
19
20 def __init__(self, opener, ui, root):
20 def __init__(self, opener, ui, root):
21 self._opener = opener
21 self._opener = opener
22 self._root = root
22 self._root = root
23 self._dirty = False
23 self._dirty = False
24 self._dirtypl = False
24 self._dirtypl = False
25 self._ui = ui
25 self._ui = ui
26
26
27 def __getattr__(self, name):
27 def __getattr__(self, name):
28 if name == '_map':
28 if name == '_map':
29 self._read()
29 self._read()
30 return self._map
30 return self._map
31 elif name == '_copymap':
31 elif name == '_copymap':
32 self._read()
32 self._read()
33 return self._copymap
33 return self._copymap
34 elif name == '_branch':
34 elif name == '_branch':
35 try:
35 try:
36 self._branch = (self._opener("branch").read().strip()
36 self._branch = (self._opener("branch").read().strip()
37 or "default")
37 or "default")
38 except IOError:
38 except IOError:
39 self._branch = "default"
39 self._branch = "default"
40 return self._branch
40 return self._branch
41 elif name == '_pl':
41 elif name == '_pl':
42 self._pl = [nullid, nullid]
42 self._pl = [nullid, nullid]
43 try:
43 try:
44 st = self._opener("dirstate").read(40)
44 st = self._opener("dirstate").read(40)
45 if len(st) == 40:
45 if len(st) == 40:
46 self._pl = st[:20], st[20:40]
46 self._pl = st[:20], st[20:40]
47 except IOError, err:
47 except IOError, err:
48 if err.errno != errno.ENOENT: raise
48 if err.errno != errno.ENOENT: raise
49 return self._pl
49 return self._pl
50 elif name == '_dirs':
50 elif name == '_dirs':
51 self._dirs = {}
51 self._dirs = {}
52 for f in self._map:
52 for f in self._map:
53 if self[f] != 'r':
53 if self[f] != 'r':
54 self._incpath(f)
54 self._incpath(f)
55 return self._dirs
55 return self._dirs
56 elif name == '_ignore':
56 elif name == '_ignore':
57 files = [self._join('.hgignore')]
57 files = [self._join('.hgignore')]
58 for name, path in self._ui.configitems("ui"):
58 for name, path in self._ui.configitems("ui"):
59 if name == 'ignore' or name.startswith('ignore.'):
59 if name == 'ignore' or name.startswith('ignore.'):
60 files.append(os.path.expanduser(path))
60 files.append(os.path.expanduser(path))
61 self._ignore = ignore.ignore(self._root, files, self._ui.warn)
61 self._ignore = ignore.ignore(self._root, files, self._ui.warn)
62 return self._ignore
62 return self._ignore
63 elif name == '_slash':
63 elif name == '_slash':
64 self._slash = self._ui.configbool('ui', 'slash') and os.sep != '/'
64 self._slash = self._ui.configbool('ui', 'slash') and os.sep != '/'
65 return self._slash
65 return self._slash
66 elif name == '_checkexec':
66 elif name == '_checkexec':
67 self._checkexec = util.checkexec(self._root)
67 self._checkexec = util.checkexec(self._root)
68 return self._checkexec
68 return self._checkexec
69 else:
69 else:
70 raise AttributeError, name
70 raise AttributeError, name
71
71
72 def _join(self, f):
72 def _join(self, f):
73 return os.path.join(self._root, f)
73 return os.path.join(self._root, f)
74
74
75 def getcwd(self):
75 def getcwd(self):
76 cwd = os.getcwd()
76 cwd = os.getcwd()
77 if cwd == self._root: return ''
77 if cwd == self._root: return ''
78 # self._root ends with a path separator if self._root is '/' or 'C:\'
78 # self._root ends with a path separator if self._root is '/' or 'C:\'
79 rootsep = self._root
79 rootsep = self._root
80 if not util.endswithsep(rootsep):
80 if not util.endswithsep(rootsep):
81 rootsep += os.sep
81 rootsep += os.sep
82 if cwd.startswith(rootsep):
82 if cwd.startswith(rootsep):
83 return cwd[len(rootsep):]
83 return cwd[len(rootsep):]
84 else:
84 else:
85 # we're outside the repo. return an absolute path.
85 # we're outside the repo. return an absolute path.
86 return cwd
86 return cwd
87
87
88 def pathto(self, f, cwd=None):
88 def pathto(self, f, cwd=None):
89 if cwd is None:
89 if cwd is None:
90 cwd = self.getcwd()
90 cwd = self.getcwd()
91 path = util.pathto(self._root, cwd, f)
91 path = util.pathto(self._root, cwd, f)
92 if self._slash:
92 if self._slash:
93 return util.normpath(path)
93 return util.normpath(path)
94 return path
94 return path
95
95
96 def __getitem__(self, key):
96 def __getitem__(self, key):
97 ''' current states:
97 ''' current states:
98 n normal
98 n normal
99 m needs merging
99 m needs merging
100 r marked for removal
100 r marked for removal
101 a marked for addition
101 a marked for addition
102 ? not tracked'''
102 ? not tracked'''
103 return self._map.get(key, ("?",))[0]
103 return self._map.get(key, ("?",))[0]
104
104
105 def __contains__(self, key):
105 def __contains__(self, key):
106 return key in self._map
106 return key in self._map
107
107
108 def __iter__(self):
108 def __iter__(self):
109 a = self._map.keys()
109 a = self._map.keys()
110 a.sort()
110 a.sort()
111 for x in a:
111 for x in a:
112 yield x
112 yield x
113
113
114 def parents(self):
114 def parents(self):
115 return self._pl
115 return self._pl
116
116
117 def branch(self):
117 def branch(self):
118 return self._branch
118 return self._branch
119
119
120 def setparents(self, p1, p2=nullid):
120 def setparents(self, p1, p2=nullid):
121 self._dirty = self._dirtypl = True
121 self._dirty = self._dirtypl = True
122 self._pl = p1, p2
122 self._pl = p1, p2
123
123
124 def setbranch(self, branch):
124 def setbranch(self, branch):
125 self._branch = branch
125 self._branch = branch
126 self._opener("branch", "w").write(branch + '\n')
126 self._opener("branch", "w").write(branch + '\n')
127
127
128 def _read(self):
128 def _read(self):
129 self._map = {}
129 self._map = {}
130 self._copymap = {}
130 self._copymap = {}
131 if not self._dirtypl:
131 if not self._dirtypl:
132 self._pl = [nullid, nullid]
132 self._pl = [nullid, nullid]
133 try:
133 try:
134 st = self._opener("dirstate").read()
134 st = self._opener("dirstate").read()
135 except IOError, err:
135 except IOError, err:
136 if err.errno != errno.ENOENT: raise
136 if err.errno != errno.ENOENT: raise
137 return
137 return
138 if not st:
138 if not st:
139 return
139 return
140
140
141 if not self._dirtypl:
141 if not self._dirtypl:
142 self._pl = [st[:20], st[20: 40]]
142 self._pl = [st[:20], st[20: 40]]
143
143
144 # deref fields so they will be local in loop
144 # deref fields so they will be local in loop
145 dmap = self._map
145 dmap = self._map
146 copymap = self._copymap
146 copymap = self._copymap
147 unpack = struct.unpack
147 unpack = struct.unpack
148 e_size = struct.calcsize(_format)
148 e_size = struct.calcsize(_format)
149 pos1 = 40
149 pos1 = 40
150 l = len(st)
150 l = len(st)
151
151
152 # the inner loop
152 # the inner loop
153 while pos1 < l:
153 while pos1 < l:
154 pos2 = pos1 + e_size
154 pos2 = pos1 + e_size
155 e = unpack(">cllll", st[pos1:pos2]) # a literal here is faster
155 e = unpack(">cllll", st[pos1:pos2]) # a literal here is faster
156 pos1 = pos2 + e[4]
156 pos1 = pos2 + e[4]
157 f = st[pos2:pos1]
157 f = st[pos2:pos1]
158 if '\0' in f:
158 if '\0' in f:
159 f, c = f.split('\0')
159 f, c = f.split('\0')
160 copymap[f] = c
160 copymap[f] = c
161 dmap[f] = e # we hold onto e[4] because making a subtuple is slow
161 dmap[f] = e # we hold onto e[4] because making a subtuple is slow
162
162
163 def invalidate(self):
163 def invalidate(self):
164 for a in "_map _copymap _branch _pl _dirs _ignore".split():
164 for a in "_map _copymap _branch _pl _dirs _ignore".split():
165 if a in self.__dict__:
165 if a in self.__dict__:
166 delattr(self, a)
166 delattr(self, a)
167 self._dirty = False
167 self._dirty = False
168
168
169 def copy(self, source, dest):
169 def copy(self, source, dest):
170 self._dirty = True
170 self._dirty = True
171 self._copymap[dest] = source
171 self._copymap[dest] = source
172
172
173 def copied(self, file):
173 def copied(self, file):
174 return self._copymap.get(file, None)
174 return self._copymap.get(file, None)
175
175
176 def copies(self):
176 def copies(self):
177 return self._copymap
177 return self._copymap
178
178
179 def _incpath(self, path):
179 def _incpath(self, path):
180 c = path.rfind('/')
180 c = path.rfind('/')
181 if c >= 0:
181 if c >= 0:
182 dirs = self._dirs
182 dirs = self._dirs
183 base = path[:c]
183 base = path[:c]
184 if base not in dirs:
184 if base not in dirs:
185 self._incpath(base)
185 self._incpath(base)
186 dirs[base] = 1
186 dirs[base] = 1
187 else:
187 else:
188 dirs[base] += 1
188 dirs[base] += 1
189
189
190 def _decpath(self, path):
190 def _decpath(self, path):
191 c = path.rfind('/')
191 c = path.rfind('/')
192 if c >= 0:
192 if c >= 0:
193 base = path[:c]
193 base = path[:c]
194 dirs = self._dirs
194 dirs = self._dirs
195 if dirs[base] == 1:
195 if dirs[base] == 1:
196 del dirs[base]
196 del dirs[base]
197 self._decpath(base)
197 self._decpath(base)
198 else:
198 else:
199 dirs[base] -= 1
199 dirs[base] -= 1
200
200
201 def _incpathcheck(self, f):
201 def _incpathcheck(self, f):
202 if '\r' in f or '\n' in f:
202 if '\r' in f or '\n' in f:
203 raise util.Abort(_("'\\n' and '\\r' disallowed in filenames: %r")
203 raise util.Abort(_("'\\n' and '\\r' disallowed in filenames: %r")
204 % f)
204 % f)
205 # shadows
205 # shadows
206 if f in self._dirs:
206 if f in self._dirs:
207 raise util.Abort(_('directory %r already in dirstate') % f)
207 raise util.Abort(_('directory %r already in dirstate') % f)
208 for c in strutil.rfindall(f, '/'):
208 for c in strutil.rfindall(f, '/'):
209 d = f[:c]
209 d = f[:c]
210 if d in self._dirs:
210 if d in self._dirs:
211 break
211 break
212 if d in self._map and self[d] != 'r':
212 if d in self._map and self[d] != 'r':
213 raise util.Abort(_('file %r in dirstate clashes with %r') %
213 raise util.Abort(_('file %r in dirstate clashes with %r') %
214 (d, f))
214 (d, f))
215 self._incpath(f)
215 self._incpath(f)
216
216
217 def _changepath(self, f, newstate, relaxed=False):
217 def _changepath(self, f, newstate, relaxed=False):
218 # handle upcoming path changes
218 # handle upcoming path changes
219 oldstate = self[f]
219 oldstate = self[f]
220 if oldstate not in "?r" and newstate in "?r":
220 if oldstate not in "?r" and newstate in "?r":
221 if "_dirs" in self.__dict__:
221 if "_dirs" in self.__dict__:
222 self._decpath(f)
222 self._decpath(f)
223 return
223 return
224 if oldstate in "?r" and newstate not in "?r":
224 if oldstate in "?r" and newstate not in "?r":
225 if relaxed and oldstate == '?':
225 if relaxed and oldstate == '?':
226 # XXX
226 # XXX
227 # in relaxed mode we assume the caller knows
227 # in relaxed mode we assume the caller knows
228 # what it is doing, workaround for updating
228 # what it is doing, workaround for updating
229 # dir-to-file revisions
229 # dir-to-file revisions
230 if "_dirs" in self.__dict__:
230 if "_dirs" in self.__dict__:
231 self._incpath(f)
231 self._incpath(f)
232 return
232 return
233 self._incpathcheck(f)
233 self._incpathcheck(f)
234 return
234 return
235
235
236 def normal(self, f):
236 def normal(self, f):
237 'mark a file normal and clean'
237 'mark a file normal and clean'
238 self._dirty = True
238 self._dirty = True
239 self._changepath(f, 'n', True)
239 self._changepath(f, 'n', True)
240 s = os.lstat(self._join(f))
240 s = os.lstat(self._join(f))
241 self._map[f] = ('n', s.st_mode, s.st_size, s.st_mtime, 0)
241 self._map[f] = ('n', s.st_mode, s.st_size, s.st_mtime, 0)
242 if f in self._copymap:
242 if f in self._copymap:
243 del self._copymap[f]
243 del self._copymap[f]
244
244
245 def normallookup(self, f):
245 def normallookup(self, f):
246 'mark a file normal, but possibly dirty'
246 'mark a file normal, but possibly dirty'
247 if self._pl[1] != nullid and f in self._map:
247 if self._pl[1] != nullid and f in self._map:
248 # if there is a merge going on and the file was either
248 # if there is a merge going on and the file was either
249 # in state 'm' or dirty before being removed, restore that state.
249 # in state 'm' or dirty before being removed, restore that state.
250 entry = self._map[f]
250 entry = self._map[f]
251 if entry[0] == 'r' and entry[2] in (-1, -2):
251 if entry[0] == 'r' and entry[2] in (-1, -2):
252 source = self._copymap.get(f)
252 source = self._copymap.get(f)
253 if entry[2] == -1:
253 if entry[2] == -1:
254 self.merge(f)
254 self.merge(f)
255 elif entry[2] == -2:
255 elif entry[2] == -2:
256 self.normaldirty(f)
256 self.normaldirty(f)
257 if source:
257 if source:
258 self.copy(source, f)
258 self.copy(source, f)
259 return
259 return
260 if entry[0] == 'm' or entry[0] == 'n' and entry[2] == -2:
260 if entry[0] == 'm' or entry[0] == 'n' and entry[2] == -2:
261 return
261 return
262 self._dirty = True
262 self._dirty = True
263 self._changepath(f, 'n', True)
263 self._changepath(f, 'n', True)
264 self._map[f] = ('n', 0, -1, -1, 0)
264 self._map[f] = ('n', 0, -1, -1, 0)
265 if f in self._copymap:
265 if f in self._copymap:
266 del self._copymap[f]
266 del self._copymap[f]
267
267
268 def normaldirty(self, f):
268 def normaldirty(self, f):
269 'mark a file normal, but dirty'
269 'mark a file normal, but dirty'
270 self._dirty = True
270 self._dirty = True
271 self._changepath(f, 'n', True)
271 self._changepath(f, 'n', True)
272 self._map[f] = ('n', 0, -2, -1, 0)
272 self._map[f] = ('n', 0, -2, -1, 0)
273 if f in self._copymap:
273 if f in self._copymap:
274 del self._copymap[f]
274 del self._copymap[f]
275
275
276 def add(self, f):
276 def add(self, f):
277 'mark a file added'
277 'mark a file added'
278 self._dirty = True
278 self._dirty = True
279 self._changepath(f, 'a')
279 self._changepath(f, 'a')
280 self._map[f] = ('a', 0, -1, -1, 0)
280 self._map[f] = ('a', 0, -1, -1, 0)
281 if f in self._copymap:
281 if f in self._copymap:
282 del self._copymap[f]
282 del self._copymap[f]
283
283
284 def remove(self, f):
284 def remove(self, f):
285 'mark a file removed'
285 'mark a file removed'
286 self._dirty = True
286 self._dirty = True
287 self._changepath(f, 'r')
287 self._changepath(f, 'r')
288 size = 0
288 size = 0
289 if self._pl[1] != nullid and f in self._map:
289 if self._pl[1] != nullid and f in self._map:
290 entry = self._map[f]
290 entry = self._map[f]
291 if entry[0] == 'm':
291 if entry[0] == 'm':
292 size = -1
292 size = -1
293 elif entry[0] == 'n' and entry[2] == -2:
293 elif entry[0] == 'n' and entry[2] == -2:
294 size = -2
294 size = -2
295 self._map[f] = ('r', 0, size, 0, 0)
295 self._map[f] = ('r', 0, size, 0, 0)
296 if size == 0 and f in self._copymap:
296 if size == 0 and f in self._copymap:
297 del self._copymap[f]
297 del self._copymap[f]
298
298
299 def merge(self, f):
299 def merge(self, f):
300 'mark a file merged'
300 'mark a file merged'
301 self._dirty = True
301 self._dirty = True
302 s = os.lstat(self._join(f))
302 s = os.lstat(self._join(f))
303 self._changepath(f, 'm', True)
303 self._changepath(f, 'm', True)
304 self._map[f] = ('m', s.st_mode, s.st_size, s.st_mtime, 0)
304 self._map[f] = ('m', s.st_mode, s.st_size, s.st_mtime, 0)
305 if f in self._copymap:
305 if f in self._copymap:
306 del self._copymap[f]
306 del self._copymap[f]
307
307
308 def forget(self, f):
308 def forget(self, f):
309 'forget a file'
309 'forget a file'
310 self._dirty = True
310 self._dirty = True
311 try:
311 try:
312 self._changepath(f, '?')
312 self._changepath(f, '?')
313 del self._map[f]
313 del self._map[f]
314 except KeyError:
314 except KeyError:
315 self._ui.warn(_("not in dirstate: %s\n") % f)
315 self._ui.warn(_("not in dirstate: %s\n") % f)
316
316
317 def clear(self):
317 def clear(self):
318 self._map = {}
318 self._map = {}
319 if "_dirs" in self.__dict__:
319 if "_dirs" in self.__dict__:
320 delattr(self, "_dirs");
320 delattr(self, "_dirs");
321 self._copymap = {}
321 self._copymap = {}
322 self._pl = [nullid, nullid]
322 self._pl = [nullid, nullid]
323 self._dirty = True
323 self._dirty = True
324
324
325 def rebuild(self, parent, files):
325 def rebuild(self, parent, files):
326 self.clear()
326 self.clear()
327 for f in files:
327 for f in files:
328 if files.execf(f):
328 if files.execf(f):
329 self._map[f] = ('n', 0777, -1, 0, 0)
329 self._map[f] = ('n', 0777, -1, 0, 0)
330 else:
330 else:
331 self._map[f] = ('n', 0666, -1, 0, 0)
331 self._map[f] = ('n', 0666, -1, 0, 0)
332 self._pl = (parent, nullid)
332 self._pl = (parent, nullid)
333 self._dirty = True
333 self._dirty = True
334
334
335 def write(self):
335 def write(self):
336 if not self._dirty:
336 if not self._dirty:
337 return
337 return
338 st = self._opener("dirstate", "w", atomictemp=True)
338 st = self._opener("dirstate", "w", atomictemp=True)
339
339
340 try:
340 try:
341 gran = int(self._ui.config('dirstate', 'granularity', 1))
341 gran = int(self._ui.config('dirstate', 'granularity', 1))
342 except ValueError:
342 except ValueError:
343 gran = 1
343 gran = 1
344 limit = sys.maxint
344 limit = sys.maxint
345 if gran > 0:
345 if gran > 0:
346 limit = util.fstat(st).st_mtime - gran
346 limit = util.fstat(st).st_mtime - gran
347
347
348 cs = cStringIO.StringIO()
348 cs = cStringIO.StringIO()
349 copymap = self._copymap
349 copymap = self._copymap
350 pack = struct.pack
350 pack = struct.pack
351 write = cs.write
351 write = cs.write
352 write("".join(self._pl))
352 write("".join(self._pl))
353 for f, e in self._map.iteritems():
353 for f, e in self._map.iteritems():
354 if f in copymap:
354 if f in copymap:
355 f = "%s\0%s" % (f, copymap[f])
355 f = "%s\0%s" % (f, copymap[f])
356 if e[3] > limit and e[0] == 'n':
356 if e[3] > limit and e[0] == 'n':
357 e = (e[0], 0, -1, -1, 0)
357 e = (e[0], 0, -1, -1, 0)
358 e = pack(_format, e[0], e[1], e[2], e[3], len(f))
358 e = pack(_format, e[0], e[1], e[2], e[3], len(f))
359 write(e)
359 write(e)
360 write(f)
360 write(f)
361 st.write(cs.getvalue())
361 st.write(cs.getvalue())
362 st.rename()
362 st.rename()
363 self._dirty = self._dirtypl = False
363 self._dirty = self._dirtypl = False
364
364
365 def _filter(self, files):
365 def _filter(self, files):
366 ret = {}
366 ret = {}
367 unknown = []
367 unknown = []
368
368
369 for x in files:
369 for x in files:
370 if x == '.':
370 if x == '.':
371 return self._map.copy()
371 return self._map.copy()
372 if x not in self._map:
372 if x not in self._map:
373 unknown.append(x)
373 unknown.append(x)
374 else:
374 else:
375 ret[x] = self._map[x]
375 ret[x] = self._map[x]
376
376
377 if not unknown:
377 if not unknown:
378 return ret
378 return ret
379
379
380 b = self._map.keys()
380 b = self._map.keys()
381 b.sort()
381 b.sort()
382 blen = len(b)
382 blen = len(b)
383
383
384 for x in unknown:
384 for x in unknown:
385 bs = bisect.bisect(b, "%s%s" % (x, '/'))
385 bs = bisect.bisect(b, "%s%s" % (x, '/'))
386 while bs < blen:
386 while bs < blen:
387 s = b[bs]
387 s = b[bs]
388 if len(s) > len(x) and s.startswith(x):
388 if len(s) > len(x) and s.startswith(x):
389 ret[s] = self._map[s]
389 ret[s] = self._map[s]
390 else:
390 else:
391 break
391 break
392 bs += 1
392 bs += 1
393 return ret
393 return ret
394
394
395 def _supported(self, f, mode, verbose=False):
395 def _supported(self, f, mode, verbose=False):
396 if stat.S_ISREG(mode) or stat.S_ISLNK(mode):
396 if stat.S_ISREG(mode) or stat.S_ISLNK(mode):
397 return True
397 return True
398 if verbose:
398 if verbose:
399 kind = 'unknown'
399 kind = 'unknown'
400 if stat.S_ISCHR(mode): kind = _('character device')
400 if stat.S_ISCHR(mode): kind = _('character device')
401 elif stat.S_ISBLK(mode): kind = _('block device')
401 elif stat.S_ISBLK(mode): kind = _('block device')
402 elif stat.S_ISFIFO(mode): kind = _('fifo')
402 elif stat.S_ISFIFO(mode): kind = _('fifo')
403 elif stat.S_ISSOCK(mode): kind = _('socket')
403 elif stat.S_ISSOCK(mode): kind = _('socket')
404 elif stat.S_ISDIR(mode): kind = _('directory')
404 elif stat.S_ISDIR(mode): kind = _('directory')
405 self._ui.warn(_('%s: unsupported file type (type is %s)\n')
405 self._ui.warn(_('%s: unsupported file type (type is %s)\n')
406 % (self.pathto(f), kind))
406 % (self.pathto(f), kind))
407 return False
407 return False
408
408
409 def _dirignore(self, f):
409 def _dirignore(self, f):
410 if f == '.':
410 if f == '.':
411 return False
411 return False
412 if self._ignore(f):
412 if self._ignore(f):
413 return True
413 return True
414 for c in strutil.findall(f, '/'):
414 for c in strutil.findall(f, '/'):
415 if self._ignore(f[:c]):
415 if self._ignore(f[:c]):
416 return True
416 return True
417 return False
417 return False
418
418
419 def walk(self, match):
419 def walk(self, match):
420 # filter out the src and stat
420 # filter out the src and stat
421 for src, f, st in self.statwalk(match.files(), match):
421 for src, f, st in self.statwalk(match.files(), match):
422 yield f
422 yield f
423
423
424 def statwalk(self, files, match, unknown=True, ignored=False):
424 def statwalk(self, files, match, unknown=True, ignored=False):
425 '''
425 '''
426 walk recursively through the directory tree, finding all files
426 walk recursively through the directory tree, finding all files
427 matched by the match function
427 matched by the match function
428
428
429 results are yielded in a tuple (src, filename, st), where src
429 results are yielded in a tuple (src, filename, st), where src
430 is one of:
430 is one of:
431 'f' the file was found in the directory tree
431 'f' the file was found in the directory tree
432 'm' the file was only in the dirstate and not in the tree
432 'm' the file was only in the dirstate and not in the tree
433
433
434 and st is the stat result if the file was found in the directory.
434 and st is the stat result if the file was found in the directory.
435 '''
435 '''
436
436
437 def fwarn(f, msg):
437 def fwarn(f, msg):
438 self._ui.warn('%s: %s\n' % (self.pathto(ff), msg))
438 self._ui.warn('%s: %s\n' % (self.pathto(ff), msg))
439 return False
439 return False
440 badfn = fwarn
440 badfn = fwarn
441 if hasattr(match, 'bad'):
441 if hasattr(match, 'bad'):
442 badfn = match.bad
442 badfn = match.bad
443
443
444 # walk all files by default
444 # walk all files by default
445 if not files:
445 if not files:
446 files = ['.']
446 files = ['.']
447 dc = self._map.copy()
447 dc = self._map.copy()
448 else:
448 else:
449 files = util.unique(files)
449 files = util.unique(files)
450 dc = self._filter(files)
450 dc = self._filter(files)
451
451
452 def imatch(file_):
452 def imatch(file_):
453 if file_ not in dc and self._ignore(file_):
453 if file_ not in dc and self._ignore(file_):
454 return False
454 return False
455 return match(file_)
455 return match(file_)
456
456
457 # TODO: don't walk unknown directories if unknown and ignored are False
457 # TODO: don't walk unknown directories if unknown and ignored are False
458 ignore = self._ignore
458 ignore = self._ignore
459 dirignore = self._dirignore
459 dirignore = self._dirignore
460 if ignored:
460 if ignored:
461 imatch = match
461 imatch = match
462 ignore = util.never
462 ignore = util.never
463 dirignore = util.never
463 dirignore = util.never
464
464
465 # self._root may end with a path separator when self._root == '/'
465 # self._root may end with a path separator when self._root == '/'
466 common_prefix_len = len(self._root)
466 common_prefix_len = len(self._root)
467 if not util.endswithsep(self._root):
467 if not util.endswithsep(self._root):
468 common_prefix_len += 1
468 common_prefix_len += 1
469
469
470 normpath = util.normpath
470 normpath = util.normpath
471 listdir = osutil.listdir
471 listdir = osutil.listdir
472 lstat = os.lstat
472 lstat = os.lstat
473 bisect_left = bisect.bisect_left
473 bisect_left = bisect.bisect_left
474 isdir = os.path.isdir
474 isdir = os.path.isdir
475 pconvert = util.pconvert
475 pconvert = util.pconvert
476 join = os.path.join
476 join = os.path.join
477 s_isdir = stat.S_ISDIR
477 s_isdir = stat.S_ISDIR
478 supported = self._supported
478 supported = self._supported
479 _join = self._join
479 _join = self._join
480 known = {'.hg': 1}
480 known = {'.hg': 1}
481
481
482 # recursion free walker, faster than os.walk.
482 # recursion free walker, faster than os.walk.
483 def findfiles(s):
483 def findfiles(s):
484 work = [s]
484 work = [s]
485 wadd = work.append
485 wadd = work.append
486 found = []
486 found = []
487 add = found.append
487 add = found.append
488 if hasattr(match, 'dir'):
488 if hasattr(match, 'dir'):
489 match.dir(normpath(s[common_prefix_len:]))
489 match.dir(normpath(s[common_prefix_len:]))
490 while work:
490 while work:
491 top = work.pop()
491 top = work.pop()
492 entries = listdir(top, stat=True)
492 entries = listdir(top, stat=True)
493 # nd is the top of the repository dir tree
493 # nd is the top of the repository dir tree
494 nd = normpath(top[common_prefix_len:])
494 nd = normpath(top[common_prefix_len:])
495 if nd == '.':
495 if nd == '.':
496 nd = ''
496 nd = ''
497 else:
497 else:
498 # do not recurse into a repo contained in this
498 # do not recurse into a repo contained in this
499 # one. use bisect to find .hg directory so speed
499 # one. use bisect to find .hg directory so speed
500 # is good on big directory.
500 # is good on big directory.
501 names = [e[0] for e in entries]
501 names = [e[0] for e in entries]
502 hg = bisect_left(names, '.hg')
502 hg = bisect_left(names, '.hg')
503 if hg < len(names) and names[hg] == '.hg':
503 if hg < len(names) and names[hg] == '.hg':
504 if isdir(join(top, '.hg')):
504 if isdir(join(top, '.hg')):
505 continue
505 continue
506 for f, kind, st in entries:
506 for f, kind, st in entries:
507 np = pconvert(join(nd, f))
507 np = pconvert(join(nd, f))
508 if np in known:
508 if np in known:
509 continue
509 continue
510 known[np] = 1
510 known[np] = 1
511 p = join(top, f)
511 p = join(top, f)
512 # don't trip over symlinks
512 # don't trip over symlinks
513 if kind == stat.S_IFDIR:
513 if kind == stat.S_IFDIR:
514 if not ignore(np):
514 if not ignore(np):
515 wadd(p)
515 wadd(p)
516 if hasattr(match, 'dir'):
516 if hasattr(match, 'dir'):
517 match.dir(np)
517 match.dir(np)
518 if np in dc and match(np):
518 if np in dc and match(np):
519 add((np, 'm', st))
519 add((np, 'm', st))
520 elif imatch(np):
520 elif imatch(np):
521 if supported(np, st.st_mode):
521 if supported(np, st.st_mode):
522 add((np, 'f', st))
522 add((np, 'f', st))
523 elif np in dc:
523 elif np in dc:
524 add((np, 'm', st))
524 add((np, 'm', st))
525 found.sort()
525 found.sort()
526 return found
526 return found
527
527
528 # step one, find all files that match our criteria
528 # step one, find all files that match our criteria
529 files.sort()
529 files.sort()
530 for ff in files:
530 for ff in files:
531 nf = normpath(ff)
531 nf = normpath(ff)
532 f = _join(ff)
532 f = _join(ff)
533 try:
533 try:
534 st = lstat(f)
534 st = lstat(f)
535 except OSError, inst:
535 except OSError, inst:
536 found = False
536 found = False
537 for fn in dc:
537 for fn in dc:
538 if nf == fn or (fn.startswith(nf) and fn[len(nf)] == '/'):
538 if nf == fn or (fn.startswith(nf) and fn[len(nf)] == '/'):
539 found = True
539 found = True
540 break
540 break
541 if not found:
541 if not found:
542 if inst.errno != errno.ENOENT:
542 if inst.errno != errno.ENOENT:
543 fwarn(ff, inst.strerror)
543 fwarn(ff, inst.strerror)
544 elif badfn(ff, inst.strerror) and imatch(nf):
544 elif badfn(ff, inst.strerror) and imatch(nf):
545 yield 'f', ff, None
545 yield 'f', ff, None
546 continue
546 continue
547 if s_isdir(st.st_mode):
547 if s_isdir(st.st_mode):
548 if not dirignore(nf):
548 if not dirignore(nf):
549 for f, src, st in findfiles(f):
549 for f, src, st in findfiles(f):
550 yield src, f, st
550 yield src, f, st
551 else:
551 else:
552 if nf in known:
552 if nf in known:
553 continue
553 continue
554 known[nf] = 1
554 known[nf] = 1
555 if match(nf):
555 if match(nf):
556 if supported(ff, st.st_mode, verbose=True):
556 if supported(ff, st.st_mode, verbose=True):
557 yield 'f', nf, st
557 yield 'f', nf, st
558 elif ff in dc:
558 elif ff in dc:
559 yield 'm', nf, st
559 yield 'm', nf, st
560
560
561 # step two run through anything left in the dc hash and yield
561 # step two run through anything left in the dc hash and yield
562 # if we haven't already seen it
562 # if we haven't already seen it
563 ks = dc.keys()
563 ks = dc.keys()
564 ks.sort()
564 ks.sort()
565 for k in ks:
565 for k in ks:
566 if k in known:
566 if k in known:
567 continue
567 continue
568 known[k] = 1
568 known[k] = 1
569 if imatch(k):
569 if imatch(k):
570 yield 'm', k, None
570 yield 'm', k, None
571
571
572 def status(self, files, match, list_ignored, list_clean, list_unknown=True):
572 def status(self, files, match, list_ignored, list_clean, list_unknown=True):
573 lookup, modified, added, unknown, ignored = [], [], [], [], []
573 lookup, modified, added, unknown, ignored = [], [], [], [], []
574 removed, deleted, clean = [], [], []
574 removed, deleted, clean = [], [], []
575
575
576 files = files or []
576 files = files or []
577 _join = self._join
577 _join = self._join
578 lstat = os.lstat
578 lstat = os.lstat
579 cmap = self._copymap
579 cmap = self._copymap
580 dmap = self._map
580 dmap = self._map
581 ladd = lookup.append
581 ladd = lookup.append
582 madd = modified.append
582 madd = modified.append
583 aadd = added.append
583 aadd = added.append
584 uadd = unknown.append
584 uadd = unknown.append
585 iadd = ignored.append
585 iadd = ignored.append
586 radd = removed.append
586 radd = removed.append
587 dadd = deleted.append
587 dadd = deleted.append
588 cadd = clean.append
588 cadd = clean.append
589
589
590 for src, fn, st in self.statwalk(files, match, unknown=list_unknown,
590 for src, fn, st in self.statwalk(files, match, unknown=list_unknown,
591 ignored=list_ignored):
591 ignored=list_ignored):
592 if fn in dmap:
592 if fn not in dmap:
593 state, mode, size, time, foo = dmap[fn]
594 else:
595 if (list_ignored or fn in files) and self._dirignore(fn):
593 if (list_ignored or fn in files) and self._dirignore(fn):
596 if list_ignored:
594 if list_ignored:
597 iadd(fn)
595 iadd(fn)
598 elif list_unknown:
596 elif list_unknown:
599 uadd(fn)
597 uadd(fn)
600 continue
598 continue
599
600 state, mode, size, time, foo = dmap[fn]
601
601 if src == 'm':
602 if src == 'm':
602 nonexistent = True
603 nonexistent = True
603 if not st:
604 if not st:
604 try:
605 try:
605 st = lstat(_join(fn))
606 st = lstat(_join(fn))
606 except OSError, inst:
607 except OSError, inst:
607 if inst.errno not in (errno.ENOENT, errno.ENOTDIR):
608 if inst.errno not in (errno.ENOENT, errno.ENOTDIR):
608 raise
609 raise
609 st = None
610 st = None
610 # We need to re-check that it is a valid file
611 # We need to re-check that it is a valid file
611 if st and self._supported(fn, st.st_mode):
612 if st and self._supported(fn, st.st_mode):
612 nonexistent = False
613 nonexistent = False
613 # XXX: what to do with file no longer present in the fs
614 # who are not removed in the dirstate ?
615 if nonexistent and state in "nma":
614 if nonexistent and state in "nma":
616 dadd(fn)
615 dadd(fn)
617 continue
616 continue
618 # check the common case first
617 # check the common case first
619 if state == 'n':
618 if state == 'n':
620 if not st:
619 if not st:
621 st = lstat(_join(fn))
620 st = lstat(_join(fn))
622 if (size >= 0 and
621 if (size >= 0 and
623 (size != st.st_size
622 (size != st.st_size
624 or ((mode ^ st.st_mode) & 0100 and self._checkexec))
623 or ((mode ^ st.st_mode) & 0100 and self._checkexec))
625 or size == -2
624 or size == -2
626 or fn in self._copymap):
625 or fn in self._copymap):
627 madd(fn)
626 madd(fn)
628 elif time != int(st.st_mtime):
627 elif time != int(st.st_mtime):
629 ladd(fn)
628 ladd(fn)
630 elif list_clean:
629 elif list_clean:
631 cadd(fn)
630 cadd(fn)
632 elif state == 'm':
631 elif state == 'm':
633 madd(fn)
632 madd(fn)
634 elif state == 'a':
633 elif state == 'a':
635 aadd(fn)
634 aadd(fn)
636 elif state == 'r':
635 elif state == 'r':
637 radd(fn)
636 radd(fn)
638
637
639 return (lookup, modified, added, removed, deleted, unknown, ignored,
638 return (lookup, modified, added, removed, deleted, unknown, ignored,
640 clean)
639 clean)
General Comments 0
You need to be logged in to leave comments. Login now