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