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