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