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