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