##// END OF EJS Templates
merge with stable
Matt Mackall -
r24632:b2fb1403 merge default
parent child Browse files
Show More
@@ -1,975 +1,982 b''
1 # dirstate.py - working directory tracking for mercurial
1 # dirstate.py - working directory tracking for mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from node import nullid
8 from node import nullid
9 from i18n import _
9 from i18n import _
10 import scmutil, util, ignore, osutil, parsers, encoding, pathutil
10 import scmutil, util, ignore, osutil, parsers, encoding, pathutil
11 import os, stat, errno
11 import os, stat, errno
12
12
13 propertycache = util.propertycache
13 propertycache = util.propertycache
14 filecache = scmutil.filecache
14 filecache = scmutil.filecache
15 _rangemask = 0x7fffffff
15 _rangemask = 0x7fffffff
16
16
17 dirstatetuple = parsers.dirstatetuple
17 dirstatetuple = parsers.dirstatetuple
18
18
19 class repocache(filecache):
19 class repocache(filecache):
20 """filecache for files in .hg/"""
20 """filecache for files in .hg/"""
21 def join(self, obj, fname):
21 def join(self, obj, fname):
22 return obj._opener.join(fname)
22 return obj._opener.join(fname)
23
23
24 class rootcache(filecache):
24 class rootcache(filecache):
25 """filecache for files in the repository root"""
25 """filecache for files in the repository root"""
26 def join(self, obj, fname):
26 def join(self, obj, fname):
27 return obj._join(fname)
27 return obj._join(fname)
28
28
29 class dirstate(object):
29 class dirstate(object):
30
30
31 def __init__(self, opener, ui, root, validate):
31 def __init__(self, opener, ui, root, validate):
32 '''Create a new dirstate object.
32 '''Create a new dirstate object.
33
33
34 opener is an open()-like callable that can be used to open the
34 opener is an open()-like callable that can be used to open the
35 dirstate file; root is the root of the directory tracked by
35 dirstate file; root is the root of the directory tracked by
36 the dirstate.
36 the dirstate.
37 '''
37 '''
38 self._opener = opener
38 self._opener = opener
39 self._validate = validate
39 self._validate = validate
40 self._root = root
40 self._root = root
41 # ntpath.join(root, '') of Python 2.7.9 does not add sep if root is
41 # ntpath.join(root, '') of Python 2.7.9 does not add sep if root is
42 # UNC path pointing to root share (issue4557)
42 # UNC path pointing to root share (issue4557)
43 if root.endswith(os.sep):
43 if root.endswith(os.sep):
44 self._rootdir = root
44 self._rootdir = root
45 else:
45 else:
46 self._rootdir = root + os.sep
46 self._rootdir = root + os.sep
47 self._dirty = False
47 self._dirty = False
48 self._dirtypl = False
48 self._dirtypl = False
49 self._lastnormaltime = 0
49 self._lastnormaltime = 0
50 self._ui = ui
50 self._ui = ui
51 self._filecache = {}
51 self._filecache = {}
52 self._parentwriters = 0
52 self._parentwriters = 0
53
53
54 def beginparentchange(self):
54 def beginparentchange(self):
55 '''Marks the beginning of a set of changes that involve changing
55 '''Marks the beginning of a set of changes that involve changing
56 the dirstate parents. If there is an exception during this time,
56 the dirstate parents. If there is an exception during this time,
57 the dirstate will not be written when the wlock is released. This
57 the dirstate will not be written when the wlock is released. This
58 prevents writing an incoherent dirstate where the parent doesn't
58 prevents writing an incoherent dirstate where the parent doesn't
59 match the contents.
59 match the contents.
60 '''
60 '''
61 self._parentwriters += 1
61 self._parentwriters += 1
62
62
63 def endparentchange(self):
63 def endparentchange(self):
64 '''Marks the end of a set of changes that involve changing the
64 '''Marks the end of a set of changes that involve changing the
65 dirstate parents. Once all parent changes have been marked done,
65 dirstate parents. Once all parent changes have been marked done,
66 the wlock will be free to write the dirstate on release.
66 the wlock will be free to write the dirstate on release.
67 '''
67 '''
68 if self._parentwriters > 0:
68 if self._parentwriters > 0:
69 self._parentwriters -= 1
69 self._parentwriters -= 1
70
70
71 def pendingparentchange(self):
71 def pendingparentchange(self):
72 '''Returns true if the dirstate is in the middle of a set of changes
72 '''Returns true if the dirstate is in the middle of a set of changes
73 that modify the dirstate parent.
73 that modify the dirstate parent.
74 '''
74 '''
75 return self._parentwriters > 0
75 return self._parentwriters > 0
76
76
77 @propertycache
77 @propertycache
78 def _map(self):
78 def _map(self):
79 '''Return the dirstate contents as a map from filename to
79 '''Return the dirstate contents as a map from filename to
80 (state, mode, size, time).'''
80 (state, mode, size, time).'''
81 self._read()
81 self._read()
82 return self._map
82 return self._map
83
83
84 @propertycache
84 @propertycache
85 def _copymap(self):
85 def _copymap(self):
86 self._read()
86 self._read()
87 return self._copymap
87 return self._copymap
88
88
89 @propertycache
89 @propertycache
90 def _filefoldmap(self):
90 def _filefoldmap(self):
91 try:
91 try:
92 makefilefoldmap = parsers.make_file_foldmap
92 makefilefoldmap = parsers.make_file_foldmap
93 except AttributeError:
93 except AttributeError:
94 pass
94 pass
95 else:
95 else:
96 return makefilefoldmap(self._map, util.normcasespec,
96 return makefilefoldmap(self._map, util.normcasespec,
97 util.normcasefallback)
97 util.normcasefallback)
98
98
99 f = {}
99 f = {}
100 normcase = util.normcase
100 normcase = util.normcase
101 for name, s in self._map.iteritems():
101 for name, s in self._map.iteritems():
102 if s[0] != 'r':
102 if s[0] != 'r':
103 f[normcase(name)] = name
103 f[normcase(name)] = name
104 f['.'] = '.' # prevents useless util.fspath() invocation
104 f['.'] = '.' # prevents useless util.fspath() invocation
105 return f
105 return f
106
106
107 @propertycache
107 @propertycache
108 def _dirfoldmap(self):
108 def _dirfoldmap(self):
109 f = {}
109 f = {}
110 normcase = util.normcase
110 normcase = util.normcase
111 for name in self._dirs:
111 for name in self._dirs:
112 f[normcase(name)] = name
112 f[normcase(name)] = name
113 return f
113 return f
114
114
115 @repocache('branch')
115 @repocache('branch')
116 def _branch(self):
116 def _branch(self):
117 try:
117 try:
118 return self._opener.read("branch").strip() or "default"
118 return self._opener.read("branch").strip() or "default"
119 except IOError, inst:
119 except IOError, inst:
120 if inst.errno != errno.ENOENT:
120 if inst.errno != errno.ENOENT:
121 raise
121 raise
122 return "default"
122 return "default"
123
123
124 @propertycache
124 @propertycache
125 def _pl(self):
125 def _pl(self):
126 try:
126 try:
127 fp = self._opener("dirstate")
127 fp = self._opener("dirstate")
128 st = fp.read(40)
128 st = fp.read(40)
129 fp.close()
129 fp.close()
130 l = len(st)
130 l = len(st)
131 if l == 40:
131 if l == 40:
132 return st[:20], st[20:40]
132 return st[:20], st[20:40]
133 elif l > 0 and l < 40:
133 elif l > 0 and l < 40:
134 raise util.Abort(_('working directory state appears damaged!'))
134 raise util.Abort(_('working directory state appears damaged!'))
135 except IOError, err:
135 except IOError, err:
136 if err.errno != errno.ENOENT:
136 if err.errno != errno.ENOENT:
137 raise
137 raise
138 return [nullid, nullid]
138 return [nullid, nullid]
139
139
140 @propertycache
140 @propertycache
141 def _dirs(self):
141 def _dirs(self):
142 return scmutil.dirs(self._map, 'r')
142 return scmutil.dirs(self._map, 'r')
143
143
144 def dirs(self):
144 def dirs(self):
145 return self._dirs
145 return self._dirs
146
146
147 @rootcache('.hgignore')
147 @rootcache('.hgignore')
148 def _ignore(self):
148 def _ignore(self):
149 files = [self._join('.hgignore')]
149 files = [self._join('.hgignore')]
150 for name, path in self._ui.configitems("ui"):
150 for name, path in self._ui.configitems("ui"):
151 if name == 'ignore' or name.startswith('ignore.'):
151 if name == 'ignore' or name.startswith('ignore.'):
152 # we need to use os.path.join here rather than self._join
152 # we need to use os.path.join here rather than self._join
153 # because path is arbitrary and user-specified
153 # because path is arbitrary and user-specified
154 files.append(os.path.join(self._rootdir, util.expandpath(path)))
154 files.append(os.path.join(self._rootdir, util.expandpath(path)))
155 return ignore.ignore(self._root, files, self._ui.warn)
155 return ignore.ignore(self._root, files, self._ui.warn)
156
156
157 @propertycache
157 @propertycache
158 def _slash(self):
158 def _slash(self):
159 return self._ui.configbool('ui', 'slash') and os.sep != '/'
159 return self._ui.configbool('ui', 'slash') and os.sep != '/'
160
160
161 @propertycache
161 @propertycache
162 def _checklink(self):
162 def _checklink(self):
163 return util.checklink(self._root)
163 return util.checklink(self._root)
164
164
165 @propertycache
165 @propertycache
166 def _checkexec(self):
166 def _checkexec(self):
167 return util.checkexec(self._root)
167 return util.checkexec(self._root)
168
168
169 @propertycache
169 @propertycache
170 def _checkcase(self):
170 def _checkcase(self):
171 return not util.checkcase(self._join('.hg'))
171 return not util.checkcase(self._join('.hg'))
172
172
173 def _join(self, f):
173 def _join(self, f):
174 # much faster than os.path.join()
174 # much faster than os.path.join()
175 # it's safe because f is always a relative path
175 # it's safe because f is always a relative path
176 return self._rootdir + f
176 return self._rootdir + f
177
177
178 def flagfunc(self, buildfallback):
178 def flagfunc(self, buildfallback):
179 if self._checklink and self._checkexec:
179 if self._checklink and self._checkexec:
180 def f(x):
180 def f(x):
181 try:
181 try:
182 st = os.lstat(self._join(x))
182 st = os.lstat(self._join(x))
183 if util.statislink(st):
183 if util.statislink(st):
184 return 'l'
184 return 'l'
185 if util.statisexec(st):
185 if util.statisexec(st):
186 return 'x'
186 return 'x'
187 except OSError:
187 except OSError:
188 pass
188 pass
189 return ''
189 return ''
190 return f
190 return f
191
191
192 fallback = buildfallback()
192 fallback = buildfallback()
193 if self._checklink:
193 if self._checklink:
194 def f(x):
194 def f(x):
195 if os.path.islink(self._join(x)):
195 if os.path.islink(self._join(x)):
196 return 'l'
196 return 'l'
197 if 'x' in fallback(x):
197 if 'x' in fallback(x):
198 return 'x'
198 return 'x'
199 return ''
199 return ''
200 return f
200 return f
201 if self._checkexec:
201 if self._checkexec:
202 def f(x):
202 def f(x):
203 if 'l' in fallback(x):
203 if 'l' in fallback(x):
204 return 'l'
204 return 'l'
205 if util.isexec(self._join(x)):
205 if util.isexec(self._join(x)):
206 return 'x'
206 return 'x'
207 return ''
207 return ''
208 return f
208 return f
209 else:
209 else:
210 return fallback
210 return fallback
211
211
212 @propertycache
212 @propertycache
213 def _cwd(self):
213 def _cwd(self):
214 return os.getcwd()
214 return os.getcwd()
215
215
216 def getcwd(self):
216 def getcwd(self):
217 cwd = self._cwd
217 cwd = self._cwd
218 if cwd == self._root:
218 if cwd == self._root:
219 return ''
219 return ''
220 # self._root ends with a path separator if self._root is '/' or 'C:\'
220 # self._root ends with a path separator if self._root is '/' or 'C:\'
221 rootsep = self._root
221 rootsep = self._root
222 if not util.endswithsep(rootsep):
222 if not util.endswithsep(rootsep):
223 rootsep += os.sep
223 rootsep += os.sep
224 if cwd.startswith(rootsep):
224 if cwd.startswith(rootsep):
225 return cwd[len(rootsep):]
225 return cwd[len(rootsep):]
226 else:
226 else:
227 # we're outside the repo. return an absolute path.
227 # we're outside the repo. return an absolute path.
228 return cwd
228 return cwd
229
229
230 def pathto(self, f, cwd=None):
230 def pathto(self, f, cwd=None):
231 if cwd is None:
231 if cwd is None:
232 cwd = self.getcwd()
232 cwd = self.getcwd()
233 path = util.pathto(self._root, cwd, f)
233 path = util.pathto(self._root, cwd, f)
234 if self._slash:
234 if self._slash:
235 return util.pconvert(path)
235 return util.pconvert(path)
236 return path
236 return path
237
237
238 def __getitem__(self, key):
238 def __getitem__(self, key):
239 '''Return the current state of key (a filename) in the dirstate.
239 '''Return the current state of key (a filename) in the dirstate.
240
240
241 States are:
241 States are:
242 n normal
242 n normal
243 m needs merging
243 m needs merging
244 r marked for removal
244 r marked for removal
245 a marked for addition
245 a marked for addition
246 ? not tracked
246 ? not tracked
247 '''
247 '''
248 return self._map.get(key, ("?",))[0]
248 return self._map.get(key, ("?",))[0]
249
249
250 def __contains__(self, key):
250 def __contains__(self, key):
251 return key in self._map
251 return key in self._map
252
252
253 def __iter__(self):
253 def __iter__(self):
254 for x in sorted(self._map):
254 for x in sorted(self._map):
255 yield x
255 yield x
256
256
257 def iteritems(self):
257 def iteritems(self):
258 return self._map.iteritems()
258 return self._map.iteritems()
259
259
260 def parents(self):
260 def parents(self):
261 return [self._validate(p) for p in self._pl]
261 return [self._validate(p) for p in self._pl]
262
262
263 def p1(self):
263 def p1(self):
264 return self._validate(self._pl[0])
264 return self._validate(self._pl[0])
265
265
266 def p2(self):
266 def p2(self):
267 return self._validate(self._pl[1])
267 return self._validate(self._pl[1])
268
268
269 def branch(self):
269 def branch(self):
270 return encoding.tolocal(self._branch)
270 return encoding.tolocal(self._branch)
271
271
272 def setparents(self, p1, p2=nullid):
272 def setparents(self, p1, p2=nullid):
273 """Set dirstate parents to p1 and p2.
273 """Set dirstate parents to p1 and p2.
274
274
275 When moving from two parents to one, 'm' merged entries a
275 When moving from two parents to one, 'm' merged entries a
276 adjusted to normal and previous copy records discarded and
276 adjusted to normal and previous copy records discarded and
277 returned by the call.
277 returned by the call.
278
278
279 See localrepo.setparents()
279 See localrepo.setparents()
280 """
280 """
281 if self._parentwriters == 0:
281 if self._parentwriters == 0:
282 raise ValueError("cannot set dirstate parent without "
282 raise ValueError("cannot set dirstate parent without "
283 "calling dirstate.beginparentchange")
283 "calling dirstate.beginparentchange")
284
284
285 self._dirty = self._dirtypl = True
285 self._dirty = self._dirtypl = True
286 oldp2 = self._pl[1]
286 oldp2 = self._pl[1]
287 self._pl = p1, p2
287 self._pl = p1, p2
288 copies = {}
288 copies = {}
289 if oldp2 != nullid and p2 == nullid:
289 if oldp2 != nullid and p2 == nullid:
290 for f, s in self._map.iteritems():
290 for f, s in self._map.iteritems():
291 # Discard 'm' markers when moving away from a merge state
291 # Discard 'm' markers when moving away from a merge state
292 if s[0] == 'm':
292 if s[0] == 'm':
293 if f in self._copymap:
293 if f in self._copymap:
294 copies[f] = self._copymap[f]
294 copies[f] = self._copymap[f]
295 self.normallookup(f)
295 self.normallookup(f)
296 # Also fix up otherparent markers
296 # Also fix up otherparent markers
297 elif s[0] == 'n' and s[2] == -2:
297 elif s[0] == 'n' and s[2] == -2:
298 if f in self._copymap:
298 if f in self._copymap:
299 copies[f] = self._copymap[f]
299 copies[f] = self._copymap[f]
300 self.add(f)
300 self.add(f)
301 return copies
301 return copies
302
302
303 def setbranch(self, branch):
303 def setbranch(self, branch):
304 self._branch = encoding.fromlocal(branch)
304 self._branch = encoding.fromlocal(branch)
305 f = self._opener('branch', 'w', atomictemp=True)
305 f = self._opener('branch', 'w', atomictemp=True)
306 try:
306 try:
307 f.write(self._branch + '\n')
307 f.write(self._branch + '\n')
308 f.close()
308 f.close()
309
309
310 # make sure filecache has the correct stat info for _branch after
310 # make sure filecache has the correct stat info for _branch after
311 # replacing the underlying file
311 # replacing the underlying file
312 ce = self._filecache['_branch']
312 ce = self._filecache['_branch']
313 if ce:
313 if ce:
314 ce.refresh()
314 ce.refresh()
315 except: # re-raises
315 except: # re-raises
316 f.discard()
316 f.discard()
317 raise
317 raise
318
318
319 def _read(self):
319 def _read(self):
320 self._map = {}
320 self._map = {}
321 self._copymap = {}
321 self._copymap = {}
322 try:
322 try:
323 st = self._opener.read("dirstate")
323 st = self._opener.read("dirstate")
324 except IOError, err:
324 except IOError, err:
325 if err.errno != errno.ENOENT:
325 if err.errno != errno.ENOENT:
326 raise
326 raise
327 return
327 return
328 if not st:
328 if not st:
329 return
329 return
330
330
331 # Python's garbage collector triggers a GC each time a certain number
331 # Python's garbage collector triggers a GC each time a certain number
332 # of container objects (the number being defined by
332 # of container objects (the number being defined by
333 # gc.get_threshold()) are allocated. parse_dirstate creates a tuple
333 # gc.get_threshold()) are allocated. parse_dirstate creates a tuple
334 # for each file in the dirstate. The C version then immediately marks
334 # for each file in the dirstate. The C version then immediately marks
335 # them as not to be tracked by the collector. However, this has no
335 # them as not to be tracked by the collector. However, this has no
336 # effect on when GCs are triggered, only on what objects the GC looks
336 # effect on when GCs are triggered, only on what objects the GC looks
337 # into. This means that O(number of files) GCs are unavoidable.
337 # into. This means that O(number of files) GCs are unavoidable.
338 # Depending on when in the process's lifetime the dirstate is parsed,
338 # Depending on when in the process's lifetime the dirstate is parsed,
339 # this can get very expensive. As a workaround, disable GC while
339 # this can get very expensive. As a workaround, disable GC while
340 # parsing the dirstate.
340 # parsing the dirstate.
341 #
341 #
342 # (we cannot decorate the function directly since it is in a C module)
342 # (we cannot decorate the function directly since it is in a C module)
343 parse_dirstate = util.nogc(parsers.parse_dirstate)
343 parse_dirstate = util.nogc(parsers.parse_dirstate)
344 p = parse_dirstate(self._map, self._copymap, st)
344 p = parse_dirstate(self._map, self._copymap, st)
345 if not self._dirtypl:
345 if not self._dirtypl:
346 self._pl = p
346 self._pl = p
347
347
348 def invalidate(self):
348 def invalidate(self):
349 for a in ("_map", "_copymap", "_filefoldmap", "_dirfoldmap", "_branch",
349 for a in ("_map", "_copymap", "_filefoldmap", "_dirfoldmap", "_branch",
350 "_pl", "_dirs", "_ignore"):
350 "_pl", "_dirs", "_ignore"):
351 if a in self.__dict__:
351 if a in self.__dict__:
352 delattr(self, a)
352 delattr(self, a)
353 self._lastnormaltime = 0
353 self._lastnormaltime = 0
354 self._dirty = False
354 self._dirty = False
355 self._parentwriters = 0
355 self._parentwriters = 0
356
356
357 def copy(self, source, dest):
357 def copy(self, source, dest):
358 """Mark dest as a copy of source. Unmark dest if source is None."""
358 """Mark dest as a copy of source. Unmark dest if source is None."""
359 if source == dest:
359 if source == dest:
360 return
360 return
361 self._dirty = True
361 self._dirty = True
362 if source is not None:
362 if source is not None:
363 self._copymap[dest] = source
363 self._copymap[dest] = source
364 elif dest in self._copymap:
364 elif dest in self._copymap:
365 del self._copymap[dest]
365 del self._copymap[dest]
366
366
367 def copied(self, file):
367 def copied(self, file):
368 return self._copymap.get(file, None)
368 return self._copymap.get(file, None)
369
369
370 def copies(self):
370 def copies(self):
371 return self._copymap
371 return self._copymap
372
372
373 def _droppath(self, f):
373 def _droppath(self, f):
374 if self[f] not in "?r" and "_dirs" in self.__dict__:
374 if self[f] not in "?r" and "_dirs" in self.__dict__:
375 self._dirs.delpath(f)
375 self._dirs.delpath(f)
376
376
377 def _addpath(self, f, state, mode, size, mtime):
377 def _addpath(self, f, state, mode, size, mtime):
378 oldstate = self[f]
378 oldstate = self[f]
379 if state == 'a' or oldstate == 'r':
379 if state == 'a' or oldstate == 'r':
380 scmutil.checkfilename(f)
380 scmutil.checkfilename(f)
381 if f in self._dirs:
381 if f in self._dirs:
382 raise util.Abort(_('directory %r already in dirstate') % f)
382 raise util.Abort(_('directory %r already in dirstate') % f)
383 # shadows
383 # shadows
384 for d in scmutil.finddirs(f):
384 for d in scmutil.finddirs(f):
385 if d in self._dirs:
385 if d in self._dirs:
386 break
386 break
387 if d in self._map and self[d] != 'r':
387 if d in self._map and self[d] != 'r':
388 raise util.Abort(
388 raise util.Abort(
389 _('file %r in dirstate clashes with %r') % (d, f))
389 _('file %r in dirstate clashes with %r') % (d, f))
390 if oldstate in "?r" and "_dirs" in self.__dict__:
390 if oldstate in "?r" and "_dirs" in self.__dict__:
391 self._dirs.addpath(f)
391 self._dirs.addpath(f)
392 self._dirty = True
392 self._dirty = True
393 self._map[f] = dirstatetuple(state, mode, size, mtime)
393 self._map[f] = dirstatetuple(state, mode, size, mtime)
394
394
395 def normal(self, f):
395 def normal(self, f):
396 '''Mark a file normal and clean.'''
396 '''Mark a file normal and clean.'''
397 s = os.lstat(self._join(f))
397 s = os.lstat(self._join(f))
398 mtime = int(s.st_mtime)
398 mtime = int(s.st_mtime)
399 self._addpath(f, 'n', s.st_mode,
399 self._addpath(f, 'n', s.st_mode,
400 s.st_size & _rangemask, mtime & _rangemask)
400 s.st_size & _rangemask, mtime & _rangemask)
401 if f in self._copymap:
401 if f in self._copymap:
402 del self._copymap[f]
402 del self._copymap[f]
403 if mtime > self._lastnormaltime:
403 if mtime > self._lastnormaltime:
404 # Remember the most recent modification timeslot for status(),
404 # Remember the most recent modification timeslot for status(),
405 # to make sure we won't miss future size-preserving file content
405 # to make sure we won't miss future size-preserving file content
406 # modifications that happen within the same timeslot.
406 # modifications that happen within the same timeslot.
407 self._lastnormaltime = mtime
407 self._lastnormaltime = mtime
408
408
409 def normallookup(self, f):
409 def normallookup(self, f):
410 '''Mark a file normal, but possibly dirty.'''
410 '''Mark a file normal, but possibly dirty.'''
411 if self._pl[1] != nullid and f in self._map:
411 if self._pl[1] != nullid and f in self._map:
412 # if there is a merge going on and the file was either
412 # if there is a merge going on and the file was either
413 # in state 'm' (-1) or coming from other parent (-2) before
413 # in state 'm' (-1) or coming from other parent (-2) before
414 # being removed, restore that state.
414 # being removed, restore that state.
415 entry = self._map[f]
415 entry = self._map[f]
416 if entry[0] == 'r' and entry[2] in (-1, -2):
416 if entry[0] == 'r' and entry[2] in (-1, -2):
417 source = self._copymap.get(f)
417 source = self._copymap.get(f)
418 if entry[2] == -1:
418 if entry[2] == -1:
419 self.merge(f)
419 self.merge(f)
420 elif entry[2] == -2:
420 elif entry[2] == -2:
421 self.otherparent(f)
421 self.otherparent(f)
422 if source:
422 if source:
423 self.copy(source, f)
423 self.copy(source, f)
424 return
424 return
425 if entry[0] == 'm' or entry[0] == 'n' and entry[2] == -2:
425 if entry[0] == 'm' or entry[0] == 'n' and entry[2] == -2:
426 return
426 return
427 self._addpath(f, 'n', 0, -1, -1)
427 self._addpath(f, 'n', 0, -1, -1)
428 if f in self._copymap:
428 if f in self._copymap:
429 del self._copymap[f]
429 del self._copymap[f]
430
430
431 def otherparent(self, f):
431 def otherparent(self, f):
432 '''Mark as coming from the other parent, always dirty.'''
432 '''Mark as coming from the other parent, always dirty.'''
433 if self._pl[1] == nullid:
433 if self._pl[1] == nullid:
434 raise util.Abort(_("setting %r to other parent "
434 raise util.Abort(_("setting %r to other parent "
435 "only allowed in merges") % f)
435 "only allowed in merges") % f)
436 if f in self and self[f] == 'n':
436 if f in self and self[f] == 'n':
437 # merge-like
437 # merge-like
438 self._addpath(f, 'm', 0, -2, -1)
438 self._addpath(f, 'm', 0, -2, -1)
439 else:
439 else:
440 # add-like
440 # add-like
441 self._addpath(f, 'n', 0, -2, -1)
441 self._addpath(f, 'n', 0, -2, -1)
442
442
443 if f in self._copymap:
443 if f in self._copymap:
444 del self._copymap[f]
444 del self._copymap[f]
445
445
446 def add(self, f):
446 def add(self, f):
447 '''Mark a file added.'''
447 '''Mark a file added.'''
448 self._addpath(f, 'a', 0, -1, -1)
448 self._addpath(f, 'a', 0, -1, -1)
449 if f in self._copymap:
449 if f in self._copymap:
450 del self._copymap[f]
450 del self._copymap[f]
451
451
452 def remove(self, f):
452 def remove(self, f):
453 '''Mark a file removed.'''
453 '''Mark a file removed.'''
454 self._dirty = True
454 self._dirty = True
455 self._droppath(f)
455 self._droppath(f)
456 size = 0
456 size = 0
457 if self._pl[1] != nullid and f in self._map:
457 if self._pl[1] != nullid and f in self._map:
458 # backup the previous state
458 # backup the previous state
459 entry = self._map[f]
459 entry = self._map[f]
460 if entry[0] == 'm': # merge
460 if entry[0] == 'm': # merge
461 size = -1
461 size = -1
462 elif entry[0] == 'n' and entry[2] == -2: # other parent
462 elif entry[0] == 'n' and entry[2] == -2: # other parent
463 size = -2
463 size = -2
464 self._map[f] = dirstatetuple('r', 0, size, 0)
464 self._map[f] = dirstatetuple('r', 0, size, 0)
465 if size == 0 and f in self._copymap:
465 if size == 0 and f in self._copymap:
466 del self._copymap[f]
466 del self._copymap[f]
467
467
468 def merge(self, f):
468 def merge(self, f):
469 '''Mark a file merged.'''
469 '''Mark a file merged.'''
470 if self._pl[1] == nullid:
470 if self._pl[1] == nullid:
471 return self.normallookup(f)
471 return self.normallookup(f)
472 return self.otherparent(f)
472 return self.otherparent(f)
473
473
474 def drop(self, f):
474 def drop(self, f):
475 '''Drop a file from the dirstate'''
475 '''Drop a file from the dirstate'''
476 if f in self._map:
476 if f in self._map:
477 self._dirty = True
477 self._dirty = True
478 self._droppath(f)
478 self._droppath(f)
479 del self._map[f]
479 del self._map[f]
480
480
481 def _discoverpath(self, path, normed, ignoremissing, exists, storemap):
481 def _discoverpath(self, path, normed, ignoremissing, exists, storemap):
482 if exists is None:
482 if exists is None:
483 exists = os.path.lexists(os.path.join(self._root, path))
483 exists = os.path.lexists(os.path.join(self._root, path))
484 if not exists:
484 if not exists:
485 # Maybe a path component exists
485 # Maybe a path component exists
486 if not ignoremissing and '/' in path:
486 if not ignoremissing and '/' in path:
487 d, f = path.rsplit('/', 1)
487 d, f = path.rsplit('/', 1)
488 d = self._normalize(d, False, ignoremissing, None)
488 d = self._normalize(d, False, ignoremissing, None)
489 folded = d + "/" + f
489 folded = d + "/" + f
490 else:
490 else:
491 # No path components, preserve original case
491 # No path components, preserve original case
492 folded = path
492 folded = path
493 else:
493 else:
494 # recursively normalize leading directory components
494 # recursively normalize leading directory components
495 # against dirstate
495 # against dirstate
496 if '/' in normed:
496 if '/' in normed:
497 d, f = normed.rsplit('/', 1)
497 d, f = normed.rsplit('/', 1)
498 d = self._normalize(d, False, ignoremissing, True)
498 d = self._normalize(d, False, ignoremissing, True)
499 r = self._root + "/" + d
499 r = self._root + "/" + d
500 folded = d + "/" + util.fspath(f, r)
500 folded = d + "/" + util.fspath(f, r)
501 else:
501 else:
502 folded = util.fspath(normed, self._root)
502 folded = util.fspath(normed, self._root)
503 storemap[normed] = folded
503 storemap[normed] = folded
504
504
505 return folded
505 return folded
506
506
507 def _normalizefile(self, path, isknown, ignoremissing=False, exists=None):
507 def _normalizefile(self, path, isknown, ignoremissing=False, exists=None):
508 normed = util.normcase(path)
508 normed = util.normcase(path)
509 folded = self._filefoldmap.get(normed, None)
509 folded = self._filefoldmap.get(normed, None)
510 if folded is None:
510 if folded is None:
511 if isknown:
511 if isknown:
512 folded = path
512 folded = path
513 else:
513 else:
514 folded = self._discoverpath(path, normed, ignoremissing, exists,
514 folded = self._discoverpath(path, normed, ignoremissing, exists,
515 self._filefoldmap)
515 self._filefoldmap)
516 return folded
516 return folded
517
517
518 def _normalize(self, path, isknown, ignoremissing=False, exists=None):
518 def _normalize(self, path, isknown, ignoremissing=False, exists=None):
519 normed = util.normcase(path)
519 normed = util.normcase(path)
520 folded = self._filefoldmap.get(normed, None)
520 folded = self._filefoldmap.get(normed, None)
521 if folded is None:
521 if folded is None:
522 folded = self._dirfoldmap.get(normed, None)
522 folded = self._dirfoldmap.get(normed, None)
523 if folded is None:
523 if folded is None:
524 if isknown:
524 if isknown:
525 folded = path
525 folded = path
526 else:
526 else:
527 # store discovered result in dirfoldmap so that future
527 # store discovered result in dirfoldmap so that future
528 # normalizefile calls don't start matching directories
528 # normalizefile calls don't start matching directories
529 folded = self._discoverpath(path, normed, ignoremissing, exists,
529 folded = self._discoverpath(path, normed, ignoremissing, exists,
530 self._dirfoldmap)
530 self._dirfoldmap)
531 return folded
531 return folded
532
532
533 def normalize(self, path, isknown=False, ignoremissing=False):
533 def normalize(self, path, isknown=False, ignoremissing=False):
534 '''
534 '''
535 normalize the case of a pathname when on a casefolding filesystem
535 normalize the case of a pathname when on a casefolding filesystem
536
536
537 isknown specifies whether the filename came from walking the
537 isknown specifies whether the filename came from walking the
538 disk, to avoid extra filesystem access.
538 disk, to avoid extra filesystem access.
539
539
540 If ignoremissing is True, missing path are returned
540 If ignoremissing is True, missing path are returned
541 unchanged. Otherwise, we try harder to normalize possibly
541 unchanged. Otherwise, we try harder to normalize possibly
542 existing path components.
542 existing path components.
543
543
544 The normalized case is determined based on the following precedence:
544 The normalized case is determined based on the following precedence:
545
545
546 - version of name already stored in the dirstate
546 - version of name already stored in the dirstate
547 - version of name stored on disk
547 - version of name stored on disk
548 - version provided via command arguments
548 - version provided via command arguments
549 '''
549 '''
550
550
551 if self._checkcase:
551 if self._checkcase:
552 return self._normalize(path, isknown, ignoremissing)
552 return self._normalize(path, isknown, ignoremissing)
553 return path
553 return path
554
554
555 def clear(self):
555 def clear(self):
556 self._map = {}
556 self._map = {}
557 if "_dirs" in self.__dict__:
557 if "_dirs" in self.__dict__:
558 delattr(self, "_dirs")
558 delattr(self, "_dirs")
559 self._copymap = {}
559 self._copymap = {}
560 self._pl = [nullid, nullid]
560 self._pl = [nullid, nullid]
561 self._lastnormaltime = 0
561 self._lastnormaltime = 0
562 self._dirty = True
562 self._dirty = True
563
563
564 def rebuild(self, parent, allfiles, changedfiles=None):
564 def rebuild(self, parent, allfiles, changedfiles=None):
565 changedfiles = changedfiles or allfiles
565 changedfiles = changedfiles or allfiles
566 oldmap = self._map
566 oldmap = self._map
567 self.clear()
567 self.clear()
568 for f in allfiles:
568 for f in allfiles:
569 if f not in changedfiles:
569 if f not in changedfiles:
570 self._map[f] = oldmap[f]
570 self._map[f] = oldmap[f]
571 else:
571 else:
572 if 'x' in allfiles.flags(f):
572 if 'x' in allfiles.flags(f):
573 self._map[f] = dirstatetuple('n', 0777, -1, 0)
573 self._map[f] = dirstatetuple('n', 0777, -1, 0)
574 else:
574 else:
575 self._map[f] = dirstatetuple('n', 0666, -1, 0)
575 self._map[f] = dirstatetuple('n', 0666, -1, 0)
576 self._pl = (parent, nullid)
576 self._pl = (parent, nullid)
577 self._dirty = True
577 self._dirty = True
578
578
579 def write(self):
579 def write(self):
580 if not self._dirty:
580 if not self._dirty:
581 return
581 return
582
582
583 # enough 'delaywrite' prevents 'pack_dirstate' from dropping
583 # enough 'delaywrite' prevents 'pack_dirstate' from dropping
584 # timestamp of each entries in dirstate, because of 'now > mtime'
584 # timestamp of each entries in dirstate, because of 'now > mtime'
585 delaywrite = self._ui.configint('debug', 'dirstate.delaywrite', 0)
585 delaywrite = self._ui.configint('debug', 'dirstate.delaywrite', 0)
586 if delaywrite > 0:
586 if delaywrite > 0:
587 import time # to avoid useless import
587 import time # to avoid useless import
588 time.sleep(delaywrite)
588 time.sleep(delaywrite)
589
589
590 st = self._opener("dirstate", "w", atomictemp=True)
590 st = self._opener("dirstate", "w", atomictemp=True)
591 # use the modification time of the newly created temporary file as the
591 # use the modification time of the newly created temporary file as the
592 # filesystem's notion of 'now'
592 # filesystem's notion of 'now'
593 now = util.fstat(st).st_mtime
593 now = util.fstat(st).st_mtime
594 st.write(parsers.pack_dirstate(self._map, self._copymap, self._pl, now))
594 st.write(parsers.pack_dirstate(self._map, self._copymap, self._pl, now))
595 st.close()
595 st.close()
596 self._lastnormaltime = 0
596 self._lastnormaltime = 0
597 self._dirty = self._dirtypl = False
597 self._dirty = self._dirtypl = False
598
598
599 def _dirignore(self, f):
599 def _dirignore(self, f):
600 if f == '.':
600 if f == '.':
601 return False
601 return False
602 if self._ignore(f):
602 if self._ignore(f):
603 return True
603 return True
604 for p in scmutil.finddirs(f):
604 for p in scmutil.finddirs(f):
605 if self._ignore(p):
605 if self._ignore(p):
606 return True
606 return True
607 return False
607 return False
608
608
609 def _walkexplicit(self, match, subrepos):
609 def _walkexplicit(self, match, subrepos):
610 '''Get stat data about the files explicitly specified by match.
610 '''Get stat data about the files explicitly specified by match.
611
611
612 Return a triple (results, dirsfound, dirsnotfound).
612 Return a triple (results, dirsfound, dirsnotfound).
613 - results is a mapping from filename to stat result. It also contains
613 - results is a mapping from filename to stat result. It also contains
614 listings mapping subrepos and .hg to None.
614 listings mapping subrepos and .hg to None.
615 - dirsfound is a list of files found to be directories.
615 - dirsfound is a list of files found to be directories.
616 - dirsnotfound is a list of files that the dirstate thinks are
616 - dirsnotfound is a list of files that the dirstate thinks are
617 directories and that were not found.'''
617 directories and that were not found.'''
618
618
619 def badtype(mode):
619 def badtype(mode):
620 kind = _('unknown')
620 kind = _('unknown')
621 if stat.S_ISCHR(mode):
621 if stat.S_ISCHR(mode):
622 kind = _('character device')
622 kind = _('character device')
623 elif stat.S_ISBLK(mode):
623 elif stat.S_ISBLK(mode):
624 kind = _('block device')
624 kind = _('block device')
625 elif stat.S_ISFIFO(mode):
625 elif stat.S_ISFIFO(mode):
626 kind = _('fifo')
626 kind = _('fifo')
627 elif stat.S_ISSOCK(mode):
627 elif stat.S_ISSOCK(mode):
628 kind = _('socket')
628 kind = _('socket')
629 elif stat.S_ISDIR(mode):
629 elif stat.S_ISDIR(mode):
630 kind = _('directory')
630 kind = _('directory')
631 return _('unsupported file type (type is %s)') % kind
631 return _('unsupported file type (type is %s)') % kind
632
632
633 matchedir = match.explicitdir
633 matchedir = match.explicitdir
634 badfn = match.bad
634 badfn = match.bad
635 dmap = self._map
635 dmap = self._map
636 lstat = os.lstat
636 lstat = os.lstat
637 getkind = stat.S_IFMT
637 getkind = stat.S_IFMT
638 dirkind = stat.S_IFDIR
638 dirkind = stat.S_IFDIR
639 regkind = stat.S_IFREG
639 regkind = stat.S_IFREG
640 lnkkind = stat.S_IFLNK
640 lnkkind = stat.S_IFLNK
641 join = self._join
641 join = self._join
642 dirsfound = []
642 dirsfound = []
643 foundadd = dirsfound.append
643 foundadd = dirsfound.append
644 dirsnotfound = []
644 dirsnotfound = []
645 notfoundadd = dirsnotfound.append
645 notfoundadd = dirsnotfound.append
646
646
647 if not match.isexact() and self._checkcase:
647 if not match.isexact() and self._checkcase:
648 normalize = self._normalize
648 normalize = self._normalize
649 else:
649 else:
650 normalize = None
650 normalize = None
651
651
652 files = sorted(match.files())
652 files = sorted(match.files())
653 subrepos.sort()
653 subrepos.sort()
654 i, j = 0, 0
654 i, j = 0, 0
655 while i < len(files) and j < len(subrepos):
655 while i < len(files) and j < len(subrepos):
656 subpath = subrepos[j] + "/"
656 subpath = subrepos[j] + "/"
657 if files[i] < subpath:
657 if files[i] < subpath:
658 i += 1
658 i += 1
659 continue
659 continue
660 while i < len(files) and files[i].startswith(subpath):
660 while i < len(files) and files[i].startswith(subpath):
661 del files[i]
661 del files[i]
662 j += 1
662 j += 1
663
663
664 if not files or '.' in files:
664 if not files or '.' in files:
665 files = ['.']
665 files = ['.']
666 results = dict.fromkeys(subrepos)
666 results = dict.fromkeys(subrepos)
667 results['.hg'] = None
667 results['.hg'] = None
668
668
669 alldirs = None
669 alldirs = None
670 for ff in files:
670 for ff in files:
671 # constructing the foldmap is expensive, so don't do it for the
671 # constructing the foldmap is expensive, so don't do it for the
672 # common case where files is ['.']
672 # common case where files is ['.']
673 if normalize and ff != '.':
673 if normalize and ff != '.':
674 nf = normalize(ff, False, True)
674 nf = normalize(ff, False, True)
675 else:
675 else:
676 nf = ff
676 nf = ff
677 if nf in results:
677 if nf in results:
678 continue
678 continue
679
679
680 try:
680 try:
681 st = lstat(join(nf))
681 st = lstat(join(nf))
682 kind = getkind(st.st_mode)
682 kind = getkind(st.st_mode)
683 if kind == dirkind:
683 if kind == dirkind:
684 if nf in dmap:
684 if nf in dmap:
685 # file replaced by dir on disk but still in dirstate
685 # file replaced by dir on disk but still in dirstate
686 results[nf] = None
686 results[nf] = None
687 if matchedir:
687 if matchedir:
688 matchedir(nf)
688 matchedir(nf)
689 foundadd((nf, ff))
689 foundadd((nf, ff))
690 elif kind == regkind or kind == lnkkind:
690 elif kind == regkind or kind == lnkkind:
691 results[nf] = st
691 results[nf] = st
692 else:
692 else:
693 badfn(ff, badtype(kind))
693 badfn(ff, badtype(kind))
694 if nf in dmap:
694 if nf in dmap:
695 results[nf] = None
695 results[nf] = None
696 except OSError, inst: # nf not found on disk - it is dirstate only
696 except OSError, inst: # nf not found on disk - it is dirstate only
697 if nf in dmap: # does it exactly match a missing file?
697 if nf in dmap: # does it exactly match a missing file?
698 results[nf] = None
698 results[nf] = None
699 else: # does it match a missing directory?
699 else: # does it match a missing directory?
700 if alldirs is None:
700 if alldirs is None:
701 alldirs = scmutil.dirs(dmap)
701 alldirs = scmutil.dirs(dmap)
702 if nf in alldirs:
702 if nf in alldirs:
703 if matchedir:
703 if matchedir:
704 matchedir(nf)
704 matchedir(nf)
705 notfoundadd(nf)
705 notfoundadd(nf)
706 else:
706 else:
707 badfn(ff, inst.strerror)
707 badfn(ff, inst.strerror)
708
708
709 return results, dirsfound, dirsnotfound
709 return results, dirsfound, dirsnotfound
710
710
711 def walk(self, match, subrepos, unknown, ignored, full=True):
711 def walk(self, match, subrepos, unknown, ignored, full=True):
712 '''
712 '''
713 Walk recursively through the directory tree, finding all files
713 Walk recursively through the directory tree, finding all files
714 matched by match.
714 matched by match.
715
715
716 If full is False, maybe skip some known-clean files.
716 If full is False, maybe skip some known-clean files.
717
717
718 Return a dict mapping filename to stat-like object (either
718 Return a dict mapping filename to stat-like object (either
719 mercurial.osutil.stat instance or return value of os.stat()).
719 mercurial.osutil.stat instance or return value of os.stat()).
720
720
721 '''
721 '''
722 # full is a flag that extensions that hook into walk can use -- this
722 # full is a flag that extensions that hook into walk can use -- this
723 # implementation doesn't use it at all. This satisfies the contract
723 # implementation doesn't use it at all. This satisfies the contract
724 # because we only guarantee a "maybe".
724 # because we only guarantee a "maybe".
725
725
726 if ignored:
726 if ignored:
727 ignore = util.never
727 ignore = util.never
728 dirignore = util.never
728 dirignore = util.never
729 elif unknown:
729 elif unknown:
730 ignore = self._ignore
730 ignore = self._ignore
731 dirignore = self._dirignore
731 dirignore = self._dirignore
732 else:
732 else:
733 # if not unknown and not ignored, drop dir recursion and step 2
733 # if not unknown and not ignored, drop dir recursion and step 2
734 ignore = util.always
734 ignore = util.always
735 dirignore = util.always
735 dirignore = util.always
736
736
737 matchfn = match.matchfn
737 matchfn = match.matchfn
738 matchalways = match.always()
738 matchalways = match.always()
739 matchtdir = match.traversedir
739 matchtdir = match.traversedir
740 dmap = self._map
740 dmap = self._map
741 listdir = osutil.listdir
741 listdir = osutil.listdir
742 lstat = os.lstat
742 lstat = os.lstat
743 dirkind = stat.S_IFDIR
743 dirkind = stat.S_IFDIR
744 regkind = stat.S_IFREG
744 regkind = stat.S_IFREG
745 lnkkind = stat.S_IFLNK
745 lnkkind = stat.S_IFLNK
746 join = self._join
746 join = self._join
747
747
748 exact = skipstep3 = False
748 exact = skipstep3 = False
749 if match.isexact(): # match.exact
749 if match.isexact(): # match.exact
750 exact = True
750 exact = True
751 dirignore = util.always # skip step 2
751 dirignore = util.always # skip step 2
752 elif match.files() and not match.anypats(): # match.match, no patterns
752 elif match.files() and not match.anypats(): # match.match, no patterns
753 skipstep3 = True
753 skipstep3 = True
754
754
755 if not exact and self._checkcase:
755 if not exact and self._checkcase:
756 normalize = self._normalize
756 normalize = self._normalize
757 normalizefile = self._normalizefile
757 normalizefile = self._normalizefile
758 skipstep3 = False
758 skipstep3 = False
759 else:
759 else:
760 normalize = self._normalize
760 normalize = self._normalize
761 normalizefile = None
761 normalizefile = None
762
762
763 # step 1: find all explicit files
763 # step 1: find all explicit files
764 results, work, dirsnotfound = self._walkexplicit(match, subrepos)
764 results, work, dirsnotfound = self._walkexplicit(match, subrepos)
765
765
766 skipstep3 = skipstep3 and not (work or dirsnotfound)
766 skipstep3 = skipstep3 and not (work or dirsnotfound)
767 work = [d for d in work if not dirignore(d[0])]
767 work = [d for d in work if not dirignore(d[0])]
768
768
769 # step 2: visit subdirectories
769 # step 2: visit subdirectories
770 def traverse(work, alreadynormed):
770 def traverse(work, alreadynormed):
771 wadd = work.append
771 wadd = work.append
772 while work:
772 while work:
773 nd = work.pop()
773 nd = work.pop()
774 skip = None
774 skip = None
775 if nd == '.':
775 if nd == '.':
776 nd = ''
776 nd = ''
777 else:
777 else:
778 skip = '.hg'
778 skip = '.hg'
779 try:
779 try:
780 entries = listdir(join(nd), stat=True, skip=skip)
780 entries = listdir(join(nd), stat=True, skip=skip)
781 except OSError, inst:
781 except OSError, inst:
782 if inst.errno in (errno.EACCES, errno.ENOENT):
782 if inst.errno in (errno.EACCES, errno.ENOENT):
783 match.bad(self.pathto(nd), inst.strerror)
783 match.bad(self.pathto(nd), inst.strerror)
784 continue
784 continue
785 raise
785 raise
786 for f, kind, st in entries:
786 for f, kind, st in entries:
787 if normalizefile:
787 if normalizefile:
788 # even though f might be a directory, we're only
788 # even though f might be a directory, we're only
789 # interested in comparing it to files currently in the
789 # interested in comparing it to files currently in the
790 # dmap -- therefore normalizefile is enough
790 # dmap -- therefore normalizefile is enough
791 nf = normalizefile(nd and (nd + "/" + f) or f, True,
791 nf = normalizefile(nd and (nd + "/" + f) or f, True,
792 True)
792 True)
793 else:
793 else:
794 nf = nd and (nd + "/" + f) or f
794 nf = nd and (nd + "/" + f) or f
795 if nf not in results:
795 if nf not in results:
796 if kind == dirkind:
796 if kind == dirkind:
797 if not ignore(nf):
797 if not ignore(nf):
798 if matchtdir:
798 if matchtdir:
799 matchtdir(nf)
799 matchtdir(nf)
800 wadd(nf)
800 wadd(nf)
801 if nf in dmap and (matchalways or matchfn(nf)):
801 if nf in dmap and (matchalways or matchfn(nf)):
802 results[nf] = None
802 results[nf] = None
803 elif kind == regkind or kind == lnkkind:
803 elif kind == regkind or kind == lnkkind:
804 if nf in dmap:
804 if nf in dmap:
805 if matchalways or matchfn(nf):
805 if matchalways or matchfn(nf):
806 results[nf] = st
806 results[nf] = st
807 elif ((matchalways or matchfn(nf))
807 elif ((matchalways or matchfn(nf))
808 and not ignore(nf)):
808 and not ignore(nf)):
809 # unknown file -- normalize if necessary
809 # unknown file -- normalize if necessary
810 if not alreadynormed:
810 if not alreadynormed:
811 nf = normalize(nf, False, True)
811 nf = normalize(nf, False, True)
812 results[nf] = st
812 results[nf] = st
813 elif nf in dmap and (matchalways or matchfn(nf)):
813 elif nf in dmap and (matchalways or matchfn(nf)):
814 results[nf] = None
814 results[nf] = None
815
815
816 for nd, d in work:
816 for nd, d in work:
817 # alreadynormed means that processwork doesn't have to do any
817 # alreadynormed means that processwork doesn't have to do any
818 # expensive directory normalization
818 # expensive directory normalization
819 alreadynormed = not normalize or nd == d
819 alreadynormed = not normalize or nd == d
820 traverse([d], alreadynormed)
820 traverse([d], alreadynormed)
821
821
822 for s in subrepos:
822 for s in subrepos:
823 del results[s]
823 del results[s]
824 del results['.hg']
824 del results['.hg']
825
825
826 # step 3: visit remaining files from dmap
826 # step 3: visit remaining files from dmap
827 if not skipstep3 and not exact:
827 if not skipstep3 and not exact:
828 # If a dmap file is not in results yet, it was either
828 # If a dmap file is not in results yet, it was either
829 # a) not matching matchfn b) ignored, c) missing, or d) under a
829 # a) not matching matchfn b) ignored, c) missing, or d) under a
830 # symlink directory.
830 # symlink directory.
831 if not results and matchalways:
831 if not results and matchalways:
832 visit = dmap.keys()
832 visit = dmap.keys()
833 else:
833 else:
834 visit = [f for f in dmap if f not in results and matchfn(f)]
834 visit = [f for f in dmap if f not in results and matchfn(f)]
835 visit.sort()
835 visit.sort()
836
836
837 if unknown:
837 if unknown:
838 # unknown == True means we walked all dirs under the roots
838 # unknown == True means we walked all dirs under the roots
839 # that wasn't ignored, and everything that matched was stat'ed
839 # that wasn't ignored, and everything that matched was stat'ed
840 # and is already in results.
840 # and is already in results.
841 # The rest must thus be ignored or under a symlink.
841 # The rest must thus be ignored or under a symlink.
842 audit_path = pathutil.pathauditor(self._root)
842 audit_path = pathutil.pathauditor(self._root)
843
843
844 for nf in iter(visit):
844 for nf in iter(visit):
845 # If a stat for the same file was already added with a
846 # different case, don't add one for this, since that would
847 # make it appear as if the file exists under both names
848 # on disk.
849 if (normalizefile and
850 normalizefile(nf, True, True) in results):
851 results[nf] = None
845 # Report ignored items in the dmap as long as they are not
852 # Report ignored items in the dmap as long as they are not
846 # under a symlink directory.
853 # under a symlink directory.
847 if audit_path.check(nf):
854 elif audit_path.check(nf):
848 try:
855 try:
849 results[nf] = lstat(join(nf))
856 results[nf] = lstat(join(nf))
850 # file was just ignored, no links, and exists
857 # file was just ignored, no links, and exists
851 except OSError:
858 except OSError:
852 # file doesn't exist
859 # file doesn't exist
853 results[nf] = None
860 results[nf] = None
854 else:
861 else:
855 # It's either missing or under a symlink directory
862 # It's either missing or under a symlink directory
856 # which we in this case report as missing
863 # which we in this case report as missing
857 results[nf] = None
864 results[nf] = None
858 else:
865 else:
859 # We may not have walked the full directory tree above,
866 # We may not have walked the full directory tree above,
860 # so stat and check everything we missed.
867 # so stat and check everything we missed.
861 nf = iter(visit).next
868 nf = iter(visit).next
862 for st in util.statfiles([join(i) for i in visit]):
869 for st in util.statfiles([join(i) for i in visit]):
863 results[nf()] = st
870 results[nf()] = st
864 return results
871 return results
865
872
866 def status(self, match, subrepos, ignored, clean, unknown):
873 def status(self, match, subrepos, ignored, clean, unknown):
867 '''Determine the status of the working copy relative to the
874 '''Determine the status of the working copy relative to the
868 dirstate and return a pair of (unsure, status), where status is of type
875 dirstate and return a pair of (unsure, status), where status is of type
869 scmutil.status and:
876 scmutil.status and:
870
877
871 unsure:
878 unsure:
872 files that might have been modified since the dirstate was
879 files that might have been modified since the dirstate was
873 written, but need to be read to be sure (size is the same
880 written, but need to be read to be sure (size is the same
874 but mtime differs)
881 but mtime differs)
875 status.modified:
882 status.modified:
876 files that have definitely been modified since the dirstate
883 files that have definitely been modified since the dirstate
877 was written (different size or mode)
884 was written (different size or mode)
878 status.clean:
885 status.clean:
879 files that have definitely not been modified since the
886 files that have definitely not been modified since the
880 dirstate was written
887 dirstate was written
881 '''
888 '''
882 listignored, listclean, listunknown = ignored, clean, unknown
889 listignored, listclean, listunknown = ignored, clean, unknown
883 lookup, modified, added, unknown, ignored = [], [], [], [], []
890 lookup, modified, added, unknown, ignored = [], [], [], [], []
884 removed, deleted, clean = [], [], []
891 removed, deleted, clean = [], [], []
885
892
886 dmap = self._map
893 dmap = self._map
887 ladd = lookup.append # aka "unsure"
894 ladd = lookup.append # aka "unsure"
888 madd = modified.append
895 madd = modified.append
889 aadd = added.append
896 aadd = added.append
890 uadd = unknown.append
897 uadd = unknown.append
891 iadd = ignored.append
898 iadd = ignored.append
892 radd = removed.append
899 radd = removed.append
893 dadd = deleted.append
900 dadd = deleted.append
894 cadd = clean.append
901 cadd = clean.append
895 mexact = match.exact
902 mexact = match.exact
896 dirignore = self._dirignore
903 dirignore = self._dirignore
897 checkexec = self._checkexec
904 checkexec = self._checkexec
898 copymap = self._copymap
905 copymap = self._copymap
899 lastnormaltime = self._lastnormaltime
906 lastnormaltime = self._lastnormaltime
900
907
901 # We need to do full walks when either
908 # We need to do full walks when either
902 # - we're listing all clean files, or
909 # - we're listing all clean files, or
903 # - match.traversedir does something, because match.traversedir should
910 # - match.traversedir does something, because match.traversedir should
904 # be called for every dir in the working dir
911 # be called for every dir in the working dir
905 full = listclean or match.traversedir is not None
912 full = listclean or match.traversedir is not None
906 for fn, st in self.walk(match, subrepos, listunknown, listignored,
913 for fn, st in self.walk(match, subrepos, listunknown, listignored,
907 full=full).iteritems():
914 full=full).iteritems():
908 if fn not in dmap:
915 if fn not in dmap:
909 if (listignored or mexact(fn)) and dirignore(fn):
916 if (listignored or mexact(fn)) and dirignore(fn):
910 if listignored:
917 if listignored:
911 iadd(fn)
918 iadd(fn)
912 else:
919 else:
913 uadd(fn)
920 uadd(fn)
914 continue
921 continue
915
922
916 # This is equivalent to 'state, mode, size, time = dmap[fn]' but not
923 # This is equivalent to 'state, mode, size, time = dmap[fn]' but not
917 # written like that for performance reasons. dmap[fn] is not a
924 # written like that for performance reasons. dmap[fn] is not a
918 # Python tuple in compiled builds. The CPython UNPACK_SEQUENCE
925 # Python tuple in compiled builds. The CPython UNPACK_SEQUENCE
919 # opcode has fast paths when the value to be unpacked is a tuple or
926 # opcode has fast paths when the value to be unpacked is a tuple or
920 # a list, but falls back to creating a full-fledged iterator in
927 # a list, but falls back to creating a full-fledged iterator in
921 # general. That is much slower than simply accessing and storing the
928 # general. That is much slower than simply accessing and storing the
922 # tuple members one by one.
929 # tuple members one by one.
923 t = dmap[fn]
930 t = dmap[fn]
924 state = t[0]
931 state = t[0]
925 mode = t[1]
932 mode = t[1]
926 size = t[2]
933 size = t[2]
927 time = t[3]
934 time = t[3]
928
935
929 if not st and state in "nma":
936 if not st and state in "nma":
930 dadd(fn)
937 dadd(fn)
931 elif state == 'n':
938 elif state == 'n':
932 mtime = int(st.st_mtime)
939 mtime = int(st.st_mtime)
933 if (size >= 0 and
940 if (size >= 0 and
934 ((size != st.st_size and size != st.st_size & _rangemask)
941 ((size != st.st_size and size != st.st_size & _rangemask)
935 or ((mode ^ st.st_mode) & 0100 and checkexec))
942 or ((mode ^ st.st_mode) & 0100 and checkexec))
936 or size == -2 # other parent
943 or size == -2 # other parent
937 or fn in copymap):
944 or fn in copymap):
938 madd(fn)
945 madd(fn)
939 elif time != mtime and time != mtime & _rangemask:
946 elif time != mtime and time != mtime & _rangemask:
940 ladd(fn)
947 ladd(fn)
941 elif mtime == lastnormaltime:
948 elif mtime == lastnormaltime:
942 # fn may have just been marked as normal and it may have
949 # fn may have just been marked as normal and it may have
943 # changed in the same second without changing its size.
950 # changed in the same second without changing its size.
944 # This can happen if we quickly do multiple commits.
951 # This can happen if we quickly do multiple commits.
945 # Force lookup, so we don't miss such a racy file change.
952 # Force lookup, so we don't miss such a racy file change.
946 ladd(fn)
953 ladd(fn)
947 elif listclean:
954 elif listclean:
948 cadd(fn)
955 cadd(fn)
949 elif state == 'm':
956 elif state == 'm':
950 madd(fn)
957 madd(fn)
951 elif state == 'a':
958 elif state == 'a':
952 aadd(fn)
959 aadd(fn)
953 elif state == 'r':
960 elif state == 'r':
954 radd(fn)
961 radd(fn)
955
962
956 return (lookup, scmutil.status(modified, added, removed, deleted,
963 return (lookup, scmutil.status(modified, added, removed, deleted,
957 unknown, ignored, clean))
964 unknown, ignored, clean))
958
965
959 def matches(self, match):
966 def matches(self, match):
960 '''
967 '''
961 return files in the dirstate (in whatever state) filtered by match
968 return files in the dirstate (in whatever state) filtered by match
962 '''
969 '''
963 dmap = self._map
970 dmap = self._map
964 if match.always():
971 if match.always():
965 return dmap.keys()
972 return dmap.keys()
966 files = match.files()
973 files = match.files()
967 if match.isexact():
974 if match.isexact():
968 # fast path -- filter the other way around, since typically files is
975 # fast path -- filter the other way around, since typically files is
969 # much smaller than dmap
976 # much smaller than dmap
970 return [f for f in files if f in dmap]
977 return [f for f in files if f in dmap]
971 if not match.anypats() and util.all(fn in dmap for fn in files):
978 if not match.anypats() and util.all(fn in dmap for fn in files):
972 # fast path -- all the values are known to be files, so just return
979 # fast path -- all the values are known to be files, so just return
973 # that
980 # that
974 return list(files)
981 return list(files)
975 return [f for f in dmap if match(f)]
982 return [f for f in dmap if match(f)]
@@ -1,214 +1,223 b''
1 #require icasefs
1 #require icasefs
2
2
3 $ hg debugfs | grep 'case-sensitive:'
3 $ hg debugfs | grep 'case-sensitive:'
4 case-sensitive: no
4 case-sensitive: no
5
5
6 test file addition with bad case
6 test file addition with bad case
7
7
8 $ hg init repo1
8 $ hg init repo1
9 $ cd repo1
9 $ cd repo1
10 $ echo a > a
10 $ echo a > a
11 $ hg add A
11 $ hg add A
12 adding a
12 adding a
13 $ hg st
13 $ hg st
14 A a
14 A a
15 $ hg ci -m adda
15 $ hg ci -m adda
16 $ hg manifest
16 $ hg manifest
17 a
17 a
18 $ cd ..
18 $ cd ..
19
19
20 test case collision on rename (issue750)
20 test case collision on rename (issue750)
21
21
22 $ hg init repo2
22 $ hg init repo2
23 $ cd repo2
23 $ cd repo2
24 $ echo a > a
24 $ echo a > a
25 $ hg --debug ci -Am adda
25 $ hg --debug ci -Am adda
26 adding a
26 adding a
27 committing files:
27 committing files:
28 a
28 a
29 committing manifest
29 committing manifest
30 committing changelog
30 committing changelog
31 couldn't read revision branch cache names: * (glob)
31 couldn't read revision branch cache names: * (glob)
32 committed changeset 0:07f4944404050f47db2e5c5071e0e84e7a27bba9
32 committed changeset 0:07f4944404050f47db2e5c5071e0e84e7a27bba9
33
33
34 Case-changing renames should work:
34 Case-changing renames should work:
35
35
36 $ hg mv a A
36 $ hg mv a A
37 $ hg mv A a
37 $ hg mv A a
38 $ hg st
38 $ hg st
39
39
40 addremove after case-changing rename has no effect (issue4590)
41
42 $ hg mv a A
43 $ hg addremove
44 recording removal of a as rename to A (100% similar)
45 $ hg revert --all
46 forgetting A
47 undeleting a
48
40 test changing case of path components
49 test changing case of path components
41
50
42 $ mkdir D
51 $ mkdir D
43 $ echo b > D/b
52 $ echo b > D/b
44 $ hg ci -Am addb D/b
53 $ hg ci -Am addb D/b
45 $ hg mv D/b d/b
54 $ hg mv D/b d/b
46 D/b: not overwriting - file exists
55 D/b: not overwriting - file exists
47 $ hg mv D/b d/c
56 $ hg mv D/b d/c
48 $ hg st
57 $ hg st
49 A D/c
58 A D/c
50 R D/b
59 R D/b
51 $ mv D temp
60 $ mv D temp
52 $ mv temp d
61 $ mv temp d
53 $ hg st
62 $ hg st
54 A D/c
63 A D/c
55 R D/b
64 R D/b
56 $ hg revert -aq
65 $ hg revert -aq
57 $ rm d/c
66 $ rm d/c
58 $ echo c > D/c
67 $ echo c > D/c
59 $ hg add D/c
68 $ hg add D/c
60 $ hg st
69 $ hg st
61 A D/c
70 A D/c
62 $ hg ci -m addc D/c
71 $ hg ci -m addc D/c
63 $ hg mv d/b d/e
72 $ hg mv d/b d/e
64 moving D/b to D/e (glob)
73 moving D/b to D/e (glob)
65 $ hg st
74 $ hg st
66 A D/e
75 A D/e
67 R D/b
76 R D/b
68 $ hg revert -aq
77 $ hg revert -aq
69 $ rm d/e
78 $ rm d/e
70 $ hg mv d/b D/B
79 $ hg mv d/b D/B
71 moving D/b to D/B (glob)
80 moving D/b to D/B (glob)
72 $ hg st
81 $ hg st
73 A D/B
82 A D/B
74 R D/b
83 R D/b
75 $ cd ..
84 $ cd ..
76
85
77 test case collision between revisions (issue912)
86 test case collision between revisions (issue912)
78
87
79 $ hg init repo3
88 $ hg init repo3
80 $ cd repo3
89 $ cd repo3
81 $ echo a > a
90 $ echo a > a
82 $ hg ci -Am adda
91 $ hg ci -Am adda
83 adding a
92 adding a
84 $ hg rm a
93 $ hg rm a
85 $ hg ci -Am removea
94 $ hg ci -Am removea
86 $ echo A > A
95 $ echo A > A
87
96
88 on linux hfs keeps the old case stored, force it
97 on linux hfs keeps the old case stored, force it
89
98
90 $ mv a aa
99 $ mv a aa
91 $ mv aa A
100 $ mv aa A
92 $ hg ci -Am addA
101 $ hg ci -Am addA
93 adding A
102 adding A
94
103
95 used to fail under case insensitive fs
104 used to fail under case insensitive fs
96
105
97 $ hg up -C 0
106 $ hg up -C 0
98 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
107 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
99 $ hg up -C
108 $ hg up -C
100 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
109 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
101
110
102 no clobbering of untracked files with wrong casing
111 no clobbering of untracked files with wrong casing
103
112
104 $ hg up -r null
113 $ hg up -r null
105 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
114 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
106 $ echo gold > a
115 $ echo gold > a
107 $ hg up
116 $ hg up
108 A: untracked file differs
117 A: untracked file differs
109 abort: untracked files in working directory differ from files in requested revision
118 abort: untracked files in working directory differ from files in requested revision
110 [255]
119 [255]
111 $ cat a
120 $ cat a
112 gold
121 gold
113 $ rm a
122 $ rm a
114
123
115 test that normal file in different case on target context is not
124 test that normal file in different case on target context is not
116 unlinked by largefiles extension.
125 unlinked by largefiles extension.
117
126
118 $ cat >> .hg/hgrc <<EOF
127 $ cat >> .hg/hgrc <<EOF
119 > [extensions]
128 > [extensions]
120 > largefiles=
129 > largefiles=
121 > EOF
130 > EOF
122 $ hg update -q -C 1
131 $ hg update -q -C 1
123 $ hg status -A
132 $ hg status -A
124 $ echo 'A as largefiles' > A
133 $ echo 'A as largefiles' > A
125 $ hg add --large A
134 $ hg add --large A
126 $ hg commit -m '#3'
135 $ hg commit -m '#3'
127 created new head
136 created new head
128 $ hg manifest -r 3
137 $ hg manifest -r 3
129 .hglf/A
138 .hglf/A
130 $ hg manifest -r 0
139 $ hg manifest -r 0
131 a
140 a
132 $ hg update -q -C 0
141 $ hg update -q -C 0
133 $ hg status -A
142 $ hg status -A
134 C a
143 C a
135 $ hg update -q -C 3
144 $ hg update -q -C 3
136 $ hg update -q 0
145 $ hg update -q 0
137
146
138 $ cd ..
147 $ cd ..
139
148
140 issue 3342: file in nested directory causes unexpected abort
149 issue 3342: file in nested directory causes unexpected abort
141
150
142 $ hg init issue3342
151 $ hg init issue3342
143 $ cd issue3342
152 $ cd issue3342
144
153
145 $ mkdir -p a/B/c/D
154 $ mkdir -p a/B/c/D
146 $ echo e > a/B/c/D/e
155 $ echo e > a/B/c/D/e
147 $ hg add a/B/c/D/e
156 $ hg add a/B/c/D/e
148
157
149 $ cd ..
158 $ cd ..
150
159
151 issue 3340: mq does not handle case changes correctly
160 issue 3340: mq does not handle case changes correctly
152
161
153 in addition to reported case, 'hg qrefresh' is also tested against
162 in addition to reported case, 'hg qrefresh' is also tested against
154 case changes.
163 case changes.
155
164
156 $ echo "[extensions]" >> $HGRCPATH
165 $ echo "[extensions]" >> $HGRCPATH
157 $ echo "mq=" >> $HGRCPATH
166 $ echo "mq=" >> $HGRCPATH
158
167
159 $ hg init issue3340
168 $ hg init issue3340
160 $ cd issue3340
169 $ cd issue3340
161
170
162 $ echo a > mIxEdCaSe
171 $ echo a > mIxEdCaSe
163 $ hg add mIxEdCaSe
172 $ hg add mIxEdCaSe
164 $ hg commit -m '#0'
173 $ hg commit -m '#0'
165 $ hg rename mIxEdCaSe tmp
174 $ hg rename mIxEdCaSe tmp
166 $ hg rename tmp MiXeDcAsE
175 $ hg rename tmp MiXeDcAsE
167 $ hg status -A
176 $ hg status -A
168 A MiXeDcAsE
177 A MiXeDcAsE
169 mIxEdCaSe
178 mIxEdCaSe
170 R mIxEdCaSe
179 R mIxEdCaSe
171 $ hg qnew changecase
180 $ hg qnew changecase
172 $ hg status -A
181 $ hg status -A
173 C MiXeDcAsE
182 C MiXeDcAsE
174
183
175 $ hg qpop -a
184 $ hg qpop -a
176 popping changecase
185 popping changecase
177 patch queue now empty
186 patch queue now empty
178 $ hg qnew refresh-casechange
187 $ hg qnew refresh-casechange
179 $ hg status -A
188 $ hg status -A
180 C mIxEdCaSe
189 C mIxEdCaSe
181 $ hg rename mIxEdCaSe tmp
190 $ hg rename mIxEdCaSe tmp
182 $ hg rename tmp MiXeDcAsE
191 $ hg rename tmp MiXeDcAsE
183 $ hg status -A
192 $ hg status -A
184 A MiXeDcAsE
193 A MiXeDcAsE
185 mIxEdCaSe
194 mIxEdCaSe
186 R mIxEdCaSe
195 R mIxEdCaSe
187 $ hg qrefresh
196 $ hg qrefresh
188 $ hg status -A
197 $ hg status -A
189 C MiXeDcAsE
198 C MiXeDcAsE
190
199
191 $ hg qpop -a
200 $ hg qpop -a
192 popping refresh-casechange
201 popping refresh-casechange
193 patch queue now empty
202 patch queue now empty
194 $ hg qnew refresh-pattern
203 $ hg qnew refresh-pattern
195 $ hg status
204 $ hg status
196 $ echo A > A
205 $ echo A > A
197 $ hg add
206 $ hg add
198 adding A
207 adding A
199 $ hg qrefresh a # issue 3271, qrefresh with file handled case wrong
208 $ hg qrefresh a # issue 3271, qrefresh with file handled case wrong
200 $ hg status # empty status means the qrefresh worked
209 $ hg status # empty status means the qrefresh worked
201
210
202 #if osx
211 #if osx
203
212
204 We assume anyone running the tests on a case-insensitive volume on OS
213 We assume anyone running the tests on a case-insensitive volume on OS
205 X will be using HFS+. If that's not true, this test will fail.
214 X will be using HFS+. If that's not true, this test will fail.
206
215
207 $ rm A
216 $ rm A
208 >>> open(u'a\u200c'.encode('utf-8'), 'w').write('unicode is fun')
217 >>> open(u'a\u200c'.encode('utf-8'), 'w').write('unicode is fun')
209 $ hg status
218 $ hg status
210 M A
219 M A
211
220
212 #endif
221 #endif
213
222
214 $ cd ..
223 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now