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