##// END OF EJS Templates
dirstate: disable gc while parsing the dirstate...
Siddharth Agarwal -
r18649:09699803 default
parent child Browse files
Show More
@@ -1,783 +1,799 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 import errno
7 import errno
8
8
9 from node import nullid
9 from node import nullid
10 from i18n import _
10 from i18n import _
11 import scmutil, util, ignore, osutil, parsers, encoding
11 import scmutil, util, ignore, osutil, parsers, encoding
12 import os, stat, errno
12 import os, stat, errno, gc
13
13
14 propertycache = util.propertycache
14 propertycache = util.propertycache
15 filecache = scmutil.filecache
15 filecache = scmutil.filecache
16 _rangemask = 0x7fffffff
16 _rangemask = 0x7fffffff
17
17
18 class repocache(filecache):
18 class repocache(filecache):
19 """filecache for files in .hg/"""
19 """filecache for files in .hg/"""
20 def join(self, obj, fname):
20 def join(self, obj, fname):
21 return obj._opener.join(fname)
21 return obj._opener.join(fname)
22
22
23 class rootcache(filecache):
23 class rootcache(filecache):
24 """filecache for files in the repository root"""
24 """filecache for files in the repository root"""
25 def join(self, obj, fname):
25 def join(self, obj, fname):
26 return obj._join(fname)
26 return obj._join(fname)
27
27
28 def _finddirs(path):
28 def _finddirs(path):
29 pos = path.rfind('/')
29 pos = path.rfind('/')
30 while pos != -1:
30 while pos != -1:
31 yield path[:pos]
31 yield path[:pos]
32 pos = path.rfind('/', 0, pos)
32 pos = path.rfind('/', 0, pos)
33
33
34 def _incdirs(dirs, path):
34 def _incdirs(dirs, path):
35 for base in _finddirs(path):
35 for base in _finddirs(path):
36 if base in dirs:
36 if base in dirs:
37 dirs[base] += 1
37 dirs[base] += 1
38 return
38 return
39 dirs[base] = 1
39 dirs[base] = 1
40
40
41 def _decdirs(dirs, path):
41 def _decdirs(dirs, path):
42 for base in _finddirs(path):
42 for base in _finddirs(path):
43 if dirs[base] > 1:
43 if dirs[base] > 1:
44 dirs[base] -= 1
44 dirs[base] -= 1
45 return
45 return
46 del dirs[base]
46 del dirs[base]
47
47
48 class dirstate(object):
48 class dirstate(object):
49
49
50 def __init__(self, opener, ui, root, validate):
50 def __init__(self, opener, ui, root, validate):
51 '''Create a new dirstate object.
51 '''Create a new dirstate object.
52
52
53 opener is an open()-like callable that can be used to open the
53 opener is an open()-like callable that can be used to open the
54 dirstate file; root is the root of the directory tracked by
54 dirstate file; root is the root of the directory tracked by
55 the dirstate.
55 the dirstate.
56 '''
56 '''
57 self._opener = opener
57 self._opener = opener
58 self._validate = validate
58 self._validate = validate
59 self._root = root
59 self._root = root
60 self._rootdir = os.path.join(root, '')
60 self._rootdir = os.path.join(root, '')
61 self._dirty = False
61 self._dirty = False
62 self._dirtypl = False
62 self._dirtypl = False
63 self._lastnormaltime = 0
63 self._lastnormaltime = 0
64 self._ui = ui
64 self._ui = ui
65 self._filecache = {}
65 self._filecache = {}
66
66
67 @propertycache
67 @propertycache
68 def _map(self):
68 def _map(self):
69 '''Return the dirstate contents as a map from filename to
69 '''Return the dirstate contents as a map from filename to
70 (state, mode, size, time).'''
70 (state, mode, size, time).'''
71 self._read()
71 self._read()
72 return self._map
72 return self._map
73
73
74 @propertycache
74 @propertycache
75 def _copymap(self):
75 def _copymap(self):
76 self._read()
76 self._read()
77 return self._copymap
77 return self._copymap
78
78
79 @propertycache
79 @propertycache
80 def _foldmap(self):
80 def _foldmap(self):
81 f = {}
81 f = {}
82 for name in self._map:
82 for name in self._map:
83 f[util.normcase(name)] = name
83 f[util.normcase(name)] = name
84 for name in self._dirs:
84 for name in self._dirs:
85 f[util.normcase(name)] = name
85 f[util.normcase(name)] = name
86 f['.'] = '.' # prevents useless util.fspath() invocation
86 f['.'] = '.' # prevents useless util.fspath() invocation
87 return f
87 return f
88
88
89 @repocache('branch')
89 @repocache('branch')
90 def _branch(self):
90 def _branch(self):
91 try:
91 try:
92 return self._opener.read("branch").strip() or "default"
92 return self._opener.read("branch").strip() or "default"
93 except IOError, inst:
93 except IOError, inst:
94 if inst.errno != errno.ENOENT:
94 if inst.errno != errno.ENOENT:
95 raise
95 raise
96 return "default"
96 return "default"
97
97
98 @propertycache
98 @propertycache
99 def _pl(self):
99 def _pl(self):
100 try:
100 try:
101 fp = self._opener("dirstate")
101 fp = self._opener("dirstate")
102 st = fp.read(40)
102 st = fp.read(40)
103 fp.close()
103 fp.close()
104 l = len(st)
104 l = len(st)
105 if l == 40:
105 if l == 40:
106 return st[:20], st[20:40]
106 return st[:20], st[20:40]
107 elif l > 0 and l < 40:
107 elif l > 0 and l < 40:
108 raise util.Abort(_('working directory state appears damaged!'))
108 raise util.Abort(_('working directory state appears damaged!'))
109 except IOError, err:
109 except IOError, err:
110 if err.errno != errno.ENOENT:
110 if err.errno != errno.ENOENT:
111 raise
111 raise
112 return [nullid, nullid]
112 return [nullid, nullid]
113
113
114 @propertycache
114 @propertycache
115 def _dirs(self):
115 def _dirs(self):
116 dirs = {}
116 dirs = {}
117 for f, s in self._map.iteritems():
117 for f, s in self._map.iteritems():
118 if s[0] != 'r':
118 if s[0] != 'r':
119 _incdirs(dirs, f)
119 _incdirs(dirs, f)
120 return dirs
120 return dirs
121
121
122 def dirs(self):
122 def dirs(self):
123 return self._dirs
123 return self._dirs
124
124
125 @rootcache('.hgignore')
125 @rootcache('.hgignore')
126 def _ignore(self):
126 def _ignore(self):
127 files = [self._join('.hgignore')]
127 files = [self._join('.hgignore')]
128 for name, path in self._ui.configitems("ui"):
128 for name, path in self._ui.configitems("ui"):
129 if name == 'ignore' or name.startswith('ignore.'):
129 if name == 'ignore' or name.startswith('ignore.'):
130 files.append(util.expandpath(path))
130 files.append(util.expandpath(path))
131 return ignore.ignore(self._root, files, self._ui.warn)
131 return ignore.ignore(self._root, files, self._ui.warn)
132
132
133 @propertycache
133 @propertycache
134 def _slash(self):
134 def _slash(self):
135 return self._ui.configbool('ui', 'slash') and os.sep != '/'
135 return self._ui.configbool('ui', 'slash') and os.sep != '/'
136
136
137 @propertycache
137 @propertycache
138 def _checklink(self):
138 def _checklink(self):
139 return util.checklink(self._root)
139 return util.checklink(self._root)
140
140
141 @propertycache
141 @propertycache
142 def _checkexec(self):
142 def _checkexec(self):
143 return util.checkexec(self._root)
143 return util.checkexec(self._root)
144
144
145 @propertycache
145 @propertycache
146 def _checkcase(self):
146 def _checkcase(self):
147 return not util.checkcase(self._join('.hg'))
147 return not util.checkcase(self._join('.hg'))
148
148
149 def _join(self, f):
149 def _join(self, f):
150 # much faster than os.path.join()
150 # much faster than os.path.join()
151 # it's safe because f is always a relative path
151 # it's safe because f is always a relative path
152 return self._rootdir + f
152 return self._rootdir + f
153
153
154 def flagfunc(self, buildfallback):
154 def flagfunc(self, buildfallback):
155 if self._checklink and self._checkexec:
155 if self._checklink and self._checkexec:
156 def f(x):
156 def f(x):
157 p = self._join(x)
157 p = self._join(x)
158 if os.path.islink(p):
158 if os.path.islink(p):
159 return 'l'
159 return 'l'
160 if util.isexec(p):
160 if util.isexec(p):
161 return 'x'
161 return 'x'
162 return ''
162 return ''
163 return f
163 return f
164
164
165 fallback = buildfallback()
165 fallback = buildfallback()
166 if self._checklink:
166 if self._checklink:
167 def f(x):
167 def f(x):
168 if os.path.islink(self._join(x)):
168 if os.path.islink(self._join(x)):
169 return 'l'
169 return 'l'
170 if 'x' in fallback(x):
170 if 'x' in fallback(x):
171 return 'x'
171 return 'x'
172 return ''
172 return ''
173 return f
173 return f
174 if self._checkexec:
174 if self._checkexec:
175 def f(x):
175 def f(x):
176 if 'l' in fallback(x):
176 if 'l' in fallback(x):
177 return 'l'
177 return 'l'
178 if util.isexec(self._join(x)):
178 if util.isexec(self._join(x)):
179 return 'x'
179 return 'x'
180 return ''
180 return ''
181 return f
181 return f
182 else:
182 else:
183 return fallback
183 return fallback
184
184
185 def getcwd(self):
185 def getcwd(self):
186 cwd = os.getcwd()
186 cwd = os.getcwd()
187 if cwd == self._root:
187 if cwd == self._root:
188 return ''
188 return ''
189 # self._root ends with a path separator if self._root is '/' or 'C:\'
189 # self._root ends with a path separator if self._root is '/' or 'C:\'
190 rootsep = self._root
190 rootsep = self._root
191 if not util.endswithsep(rootsep):
191 if not util.endswithsep(rootsep):
192 rootsep += os.sep
192 rootsep += os.sep
193 if cwd.startswith(rootsep):
193 if cwd.startswith(rootsep):
194 return cwd[len(rootsep):]
194 return cwd[len(rootsep):]
195 else:
195 else:
196 # we're outside the repo. return an absolute path.
196 # we're outside the repo. return an absolute path.
197 return cwd
197 return cwd
198
198
199 def pathto(self, f, cwd=None):
199 def pathto(self, f, cwd=None):
200 if cwd is None:
200 if cwd is None:
201 cwd = self.getcwd()
201 cwd = self.getcwd()
202 path = util.pathto(self._root, cwd, f)
202 path = util.pathto(self._root, cwd, f)
203 if self._slash:
203 if self._slash:
204 return util.normpath(path)
204 return util.normpath(path)
205 return path
205 return path
206
206
207 def __getitem__(self, key):
207 def __getitem__(self, key):
208 '''Return the current state of key (a filename) in the dirstate.
208 '''Return the current state of key (a filename) in the dirstate.
209
209
210 States are:
210 States are:
211 n normal
211 n normal
212 m needs merging
212 m needs merging
213 r marked for removal
213 r marked for removal
214 a marked for addition
214 a marked for addition
215 ? not tracked
215 ? not tracked
216 '''
216 '''
217 return self._map.get(key, ("?",))[0]
217 return self._map.get(key, ("?",))[0]
218
218
219 def __contains__(self, key):
219 def __contains__(self, key):
220 return key in self._map
220 return key in self._map
221
221
222 def __iter__(self):
222 def __iter__(self):
223 for x in sorted(self._map):
223 for x in sorted(self._map):
224 yield x
224 yield x
225
225
226 def parents(self):
226 def parents(self):
227 return [self._validate(p) for p in self._pl]
227 return [self._validate(p) for p in self._pl]
228
228
229 def p1(self):
229 def p1(self):
230 return self._validate(self._pl[0])
230 return self._validate(self._pl[0])
231
231
232 def p2(self):
232 def p2(self):
233 return self._validate(self._pl[1])
233 return self._validate(self._pl[1])
234
234
235 def branch(self):
235 def branch(self):
236 return encoding.tolocal(self._branch)
236 return encoding.tolocal(self._branch)
237
237
238 def setparents(self, p1, p2=nullid):
238 def setparents(self, p1, p2=nullid):
239 """Set dirstate parents to p1 and p2.
239 """Set dirstate parents to p1 and p2.
240
240
241 When moving from two parents to one, 'm' merged entries a
241 When moving from two parents to one, 'm' merged entries a
242 adjusted to normal and previous copy records discarded and
242 adjusted to normal and previous copy records discarded and
243 returned by the call.
243 returned by the call.
244
244
245 See localrepo.setparents()
245 See localrepo.setparents()
246 """
246 """
247 self._dirty = self._dirtypl = True
247 self._dirty = self._dirtypl = True
248 oldp2 = self._pl[1]
248 oldp2 = self._pl[1]
249 self._pl = p1, p2
249 self._pl = p1, p2
250 copies = {}
250 copies = {}
251 if oldp2 != nullid and p2 == nullid:
251 if oldp2 != nullid and p2 == nullid:
252 # Discard 'm' markers when moving away from a merge state
252 # Discard 'm' markers when moving away from a merge state
253 for f, s in self._map.iteritems():
253 for f, s in self._map.iteritems():
254 if s[0] == 'm':
254 if s[0] == 'm':
255 if f in self._copymap:
255 if f in self._copymap:
256 copies[f] = self._copymap[f]
256 copies[f] = self._copymap[f]
257 self.normallookup(f)
257 self.normallookup(f)
258 return copies
258 return copies
259
259
260 def setbranch(self, branch):
260 def setbranch(self, branch):
261 self._branch = encoding.fromlocal(branch)
261 self._branch = encoding.fromlocal(branch)
262 f = self._opener('branch', 'w', atomictemp=True)
262 f = self._opener('branch', 'w', atomictemp=True)
263 try:
263 try:
264 f.write(self._branch + '\n')
264 f.write(self._branch + '\n')
265 f.close()
265 f.close()
266
266
267 # make sure filecache has the correct stat info for _branch after
267 # make sure filecache has the correct stat info for _branch after
268 # replacing the underlying file
268 # replacing the underlying file
269 ce = self._filecache['_branch']
269 ce = self._filecache['_branch']
270 if ce:
270 if ce:
271 ce.refresh()
271 ce.refresh()
272 except: # re-raises
272 except: # re-raises
273 f.discard()
273 f.discard()
274 raise
274 raise
275
275
276 def _read(self):
276 def _read(self):
277 self._map = {}
277 self._map = {}
278 self._copymap = {}
278 self._copymap = {}
279 try:
279 try:
280 st = self._opener.read("dirstate")
280 st = self._opener.read("dirstate")
281 except IOError, err:
281 except IOError, err:
282 if err.errno != errno.ENOENT:
282 if err.errno != errno.ENOENT:
283 raise
283 raise
284 return
284 return
285 if not st:
285 if not st:
286 return
286 return
287
287
288 p = parsers.parse_dirstate(self._map, self._copymap, st)
288 # Python's garbage collector triggers a GC each time a certain number
289 # of container objects (the number being defined by
290 # gc.get_threshold()) are allocated. parse_dirstate creates a tuple
291 # for each file in the dirstate. The C version then immediately marks
292 # them as not to be tracked by the collector. However, this has no
293 # effect on when GCs are triggered, only on what objects the GC looks
294 # into. This means that O(number of files) GCs are unavoidable.
295 # Depending on when in the process's lifetime the dirstate is parsed,
296 # this can get very expensive. As a workaround, disable GC while
297 # parsing the dirstate.
298 gcenabled = gc.isenabled()
299 gc.disable()
300 try:
301 p = parsers.parse_dirstate(self._map, self._copymap, st)
302 finally:
303 if gcenabled:
304 gc.enable()
289 if not self._dirtypl:
305 if not self._dirtypl:
290 self._pl = p
306 self._pl = p
291
307
292 def invalidate(self):
308 def invalidate(self):
293 for a in ("_map", "_copymap", "_foldmap", "_branch", "_pl", "_dirs",
309 for a in ("_map", "_copymap", "_foldmap", "_branch", "_pl", "_dirs",
294 "_ignore"):
310 "_ignore"):
295 if a in self.__dict__:
311 if a in self.__dict__:
296 delattr(self, a)
312 delattr(self, a)
297 self._lastnormaltime = 0
313 self._lastnormaltime = 0
298 self._dirty = False
314 self._dirty = False
299
315
300 def copy(self, source, dest):
316 def copy(self, source, dest):
301 """Mark dest as a copy of source. Unmark dest if source is None."""
317 """Mark dest as a copy of source. Unmark dest if source is None."""
302 if source == dest:
318 if source == dest:
303 return
319 return
304 self._dirty = True
320 self._dirty = True
305 if source is not None:
321 if source is not None:
306 self._copymap[dest] = source
322 self._copymap[dest] = source
307 elif dest in self._copymap:
323 elif dest in self._copymap:
308 del self._copymap[dest]
324 del self._copymap[dest]
309
325
310 def copied(self, file):
326 def copied(self, file):
311 return self._copymap.get(file, None)
327 return self._copymap.get(file, None)
312
328
313 def copies(self):
329 def copies(self):
314 return self._copymap
330 return self._copymap
315
331
316 def _droppath(self, f):
332 def _droppath(self, f):
317 if self[f] not in "?r" and "_dirs" in self.__dict__:
333 if self[f] not in "?r" and "_dirs" in self.__dict__:
318 _decdirs(self._dirs, f)
334 _decdirs(self._dirs, f)
319
335
320 def _addpath(self, f, state, mode, size, mtime):
336 def _addpath(self, f, state, mode, size, mtime):
321 oldstate = self[f]
337 oldstate = self[f]
322 if state == 'a' or oldstate == 'r':
338 if state == 'a' or oldstate == 'r':
323 scmutil.checkfilename(f)
339 scmutil.checkfilename(f)
324 if f in self._dirs:
340 if f in self._dirs:
325 raise util.Abort(_('directory %r already in dirstate') % f)
341 raise util.Abort(_('directory %r already in dirstate') % f)
326 # shadows
342 # shadows
327 for d in _finddirs(f):
343 for d in _finddirs(f):
328 if d in self._dirs:
344 if d in self._dirs:
329 break
345 break
330 if d in self._map and self[d] != 'r':
346 if d in self._map and self[d] != 'r':
331 raise util.Abort(
347 raise util.Abort(
332 _('file %r in dirstate clashes with %r') % (d, f))
348 _('file %r in dirstate clashes with %r') % (d, f))
333 if oldstate in "?r" and "_dirs" in self.__dict__:
349 if oldstate in "?r" and "_dirs" in self.__dict__:
334 _incdirs(self._dirs, f)
350 _incdirs(self._dirs, f)
335 self._dirty = True
351 self._dirty = True
336 self._map[f] = (state, mode, size, mtime)
352 self._map[f] = (state, mode, size, mtime)
337
353
338 def normal(self, f):
354 def normal(self, f):
339 '''Mark a file normal and clean.'''
355 '''Mark a file normal and clean.'''
340 s = os.lstat(self._join(f))
356 s = os.lstat(self._join(f))
341 mtime = int(s.st_mtime)
357 mtime = int(s.st_mtime)
342 self._addpath(f, 'n', s.st_mode,
358 self._addpath(f, 'n', s.st_mode,
343 s.st_size & _rangemask, mtime & _rangemask)
359 s.st_size & _rangemask, mtime & _rangemask)
344 if f in self._copymap:
360 if f in self._copymap:
345 del self._copymap[f]
361 del self._copymap[f]
346 if mtime > self._lastnormaltime:
362 if mtime > self._lastnormaltime:
347 # Remember the most recent modification timeslot for status(),
363 # Remember the most recent modification timeslot for status(),
348 # to make sure we won't miss future size-preserving file content
364 # to make sure we won't miss future size-preserving file content
349 # modifications that happen within the same timeslot.
365 # modifications that happen within the same timeslot.
350 self._lastnormaltime = mtime
366 self._lastnormaltime = mtime
351
367
352 def normallookup(self, f):
368 def normallookup(self, f):
353 '''Mark a file normal, but possibly dirty.'''
369 '''Mark a file normal, but possibly dirty.'''
354 if self._pl[1] != nullid and f in self._map:
370 if self._pl[1] != nullid and f in self._map:
355 # if there is a merge going on and the file was either
371 # if there is a merge going on and the file was either
356 # in state 'm' (-1) or coming from other parent (-2) before
372 # in state 'm' (-1) or coming from other parent (-2) before
357 # being removed, restore that state.
373 # being removed, restore that state.
358 entry = self._map[f]
374 entry = self._map[f]
359 if entry[0] == 'r' and entry[2] in (-1, -2):
375 if entry[0] == 'r' and entry[2] in (-1, -2):
360 source = self._copymap.get(f)
376 source = self._copymap.get(f)
361 if entry[2] == -1:
377 if entry[2] == -1:
362 self.merge(f)
378 self.merge(f)
363 elif entry[2] == -2:
379 elif entry[2] == -2:
364 self.otherparent(f)
380 self.otherparent(f)
365 if source:
381 if source:
366 self.copy(source, f)
382 self.copy(source, f)
367 return
383 return
368 if entry[0] == 'm' or entry[0] == 'n' and entry[2] == -2:
384 if entry[0] == 'm' or entry[0] == 'n' and entry[2] == -2:
369 return
385 return
370 self._addpath(f, 'n', 0, -1, -1)
386 self._addpath(f, 'n', 0, -1, -1)
371 if f in self._copymap:
387 if f in self._copymap:
372 del self._copymap[f]
388 del self._copymap[f]
373
389
374 def otherparent(self, f):
390 def otherparent(self, f):
375 '''Mark as coming from the other parent, always dirty.'''
391 '''Mark as coming from the other parent, always dirty.'''
376 if self._pl[1] == nullid:
392 if self._pl[1] == nullid:
377 raise util.Abort(_("setting %r to other parent "
393 raise util.Abort(_("setting %r to other parent "
378 "only allowed in merges") % f)
394 "only allowed in merges") % f)
379 self._addpath(f, 'n', 0, -2, -1)
395 self._addpath(f, 'n', 0, -2, -1)
380 if f in self._copymap:
396 if f in self._copymap:
381 del self._copymap[f]
397 del self._copymap[f]
382
398
383 def add(self, f):
399 def add(self, f):
384 '''Mark a file added.'''
400 '''Mark a file added.'''
385 self._addpath(f, 'a', 0, -1, -1)
401 self._addpath(f, 'a', 0, -1, -1)
386 if f in self._copymap:
402 if f in self._copymap:
387 del self._copymap[f]
403 del self._copymap[f]
388
404
389 def remove(self, f):
405 def remove(self, f):
390 '''Mark a file removed.'''
406 '''Mark a file removed.'''
391 self._dirty = True
407 self._dirty = True
392 self._droppath(f)
408 self._droppath(f)
393 size = 0
409 size = 0
394 if self._pl[1] != nullid and f in self._map:
410 if self._pl[1] != nullid and f in self._map:
395 # backup the previous state
411 # backup the previous state
396 entry = self._map[f]
412 entry = self._map[f]
397 if entry[0] == 'm': # merge
413 if entry[0] == 'm': # merge
398 size = -1
414 size = -1
399 elif entry[0] == 'n' and entry[2] == -2: # other parent
415 elif entry[0] == 'n' and entry[2] == -2: # other parent
400 size = -2
416 size = -2
401 self._map[f] = ('r', 0, size, 0)
417 self._map[f] = ('r', 0, size, 0)
402 if size == 0 and f in self._copymap:
418 if size == 0 and f in self._copymap:
403 del self._copymap[f]
419 del self._copymap[f]
404
420
405 def merge(self, f):
421 def merge(self, f):
406 '''Mark a file merged.'''
422 '''Mark a file merged.'''
407 if self._pl[1] == nullid:
423 if self._pl[1] == nullid:
408 return self.normallookup(f)
424 return self.normallookup(f)
409 s = os.lstat(self._join(f))
425 s = os.lstat(self._join(f))
410 self._addpath(f, 'm', s.st_mode,
426 self._addpath(f, 'm', s.st_mode,
411 s.st_size & _rangemask, int(s.st_mtime) & _rangemask)
427 s.st_size & _rangemask, int(s.st_mtime) & _rangemask)
412 if f in self._copymap:
428 if f in self._copymap:
413 del self._copymap[f]
429 del self._copymap[f]
414
430
415 def drop(self, f):
431 def drop(self, f):
416 '''Drop a file from the dirstate'''
432 '''Drop a file from the dirstate'''
417 if f in self._map:
433 if f in self._map:
418 self._dirty = True
434 self._dirty = True
419 self._droppath(f)
435 self._droppath(f)
420 del self._map[f]
436 del self._map[f]
421
437
422 def _normalize(self, path, isknown, ignoremissing=False, exists=None):
438 def _normalize(self, path, isknown, ignoremissing=False, exists=None):
423 normed = util.normcase(path)
439 normed = util.normcase(path)
424 folded = self._foldmap.get(normed, None)
440 folded = self._foldmap.get(normed, None)
425 if folded is None:
441 if folded is None:
426 if isknown:
442 if isknown:
427 folded = path
443 folded = path
428 else:
444 else:
429 if exists is None:
445 if exists is None:
430 exists = os.path.lexists(os.path.join(self._root, path))
446 exists = os.path.lexists(os.path.join(self._root, path))
431 if not exists:
447 if not exists:
432 # Maybe a path component exists
448 # Maybe a path component exists
433 if not ignoremissing and '/' in path:
449 if not ignoremissing and '/' in path:
434 d, f = path.rsplit('/', 1)
450 d, f = path.rsplit('/', 1)
435 d = self._normalize(d, isknown, ignoremissing, None)
451 d = self._normalize(d, isknown, ignoremissing, None)
436 folded = d + "/" + f
452 folded = d + "/" + f
437 else:
453 else:
438 # No path components, preserve original case
454 # No path components, preserve original case
439 folded = path
455 folded = path
440 else:
456 else:
441 # recursively normalize leading directory components
457 # recursively normalize leading directory components
442 # against dirstate
458 # against dirstate
443 if '/' in normed:
459 if '/' in normed:
444 d, f = normed.rsplit('/', 1)
460 d, f = normed.rsplit('/', 1)
445 d = self._normalize(d, isknown, ignoremissing, True)
461 d = self._normalize(d, isknown, ignoremissing, True)
446 r = self._root + "/" + d
462 r = self._root + "/" + d
447 folded = d + "/" + util.fspath(f, r)
463 folded = d + "/" + util.fspath(f, r)
448 else:
464 else:
449 folded = util.fspath(normed, self._root)
465 folded = util.fspath(normed, self._root)
450 self._foldmap[normed] = folded
466 self._foldmap[normed] = folded
451
467
452 return folded
468 return folded
453
469
454 def normalize(self, path, isknown=False, ignoremissing=False):
470 def normalize(self, path, isknown=False, ignoremissing=False):
455 '''
471 '''
456 normalize the case of a pathname when on a casefolding filesystem
472 normalize the case of a pathname when on a casefolding filesystem
457
473
458 isknown specifies whether the filename came from walking the
474 isknown specifies whether the filename came from walking the
459 disk, to avoid extra filesystem access.
475 disk, to avoid extra filesystem access.
460
476
461 If ignoremissing is True, missing path are returned
477 If ignoremissing is True, missing path are returned
462 unchanged. Otherwise, we try harder to normalize possibly
478 unchanged. Otherwise, we try harder to normalize possibly
463 existing path components.
479 existing path components.
464
480
465 The normalized case is determined based on the following precedence:
481 The normalized case is determined based on the following precedence:
466
482
467 - version of name already stored in the dirstate
483 - version of name already stored in the dirstate
468 - version of name stored on disk
484 - version of name stored on disk
469 - version provided via command arguments
485 - version provided via command arguments
470 '''
486 '''
471
487
472 if self._checkcase:
488 if self._checkcase:
473 return self._normalize(path, isknown, ignoremissing)
489 return self._normalize(path, isknown, ignoremissing)
474 return path
490 return path
475
491
476 def clear(self):
492 def clear(self):
477 self._map = {}
493 self._map = {}
478 if "_dirs" in self.__dict__:
494 if "_dirs" in self.__dict__:
479 delattr(self, "_dirs")
495 delattr(self, "_dirs")
480 self._copymap = {}
496 self._copymap = {}
481 self._pl = [nullid, nullid]
497 self._pl = [nullid, nullid]
482 self._lastnormaltime = 0
498 self._lastnormaltime = 0
483 self._dirty = True
499 self._dirty = True
484
500
485 def rebuild(self, parent, files):
501 def rebuild(self, parent, files):
486 self.clear()
502 self.clear()
487 for f in files:
503 for f in files:
488 if 'x' in files.flags(f):
504 if 'x' in files.flags(f):
489 self._map[f] = ('n', 0777, -1, 0)
505 self._map[f] = ('n', 0777, -1, 0)
490 else:
506 else:
491 self._map[f] = ('n', 0666, -1, 0)
507 self._map[f] = ('n', 0666, -1, 0)
492 self._pl = (parent, nullid)
508 self._pl = (parent, nullid)
493 self._dirty = True
509 self._dirty = True
494
510
495 def write(self):
511 def write(self):
496 if not self._dirty:
512 if not self._dirty:
497 return
513 return
498 st = self._opener("dirstate", "w", atomictemp=True)
514 st = self._opener("dirstate", "w", atomictemp=True)
499
515
500 def finish(s):
516 def finish(s):
501 st.write(s)
517 st.write(s)
502 st.close()
518 st.close()
503 self._lastnormaltime = 0
519 self._lastnormaltime = 0
504 self._dirty = self._dirtypl = False
520 self._dirty = self._dirtypl = False
505
521
506 # use the modification time of the newly created temporary file as the
522 # use the modification time of the newly created temporary file as the
507 # filesystem's notion of 'now'
523 # filesystem's notion of 'now'
508 now = util.fstat(st).st_mtime
524 now = util.fstat(st).st_mtime
509 finish(parsers.pack_dirstate(self._map, self._copymap, self._pl, now))
525 finish(parsers.pack_dirstate(self._map, self._copymap, self._pl, now))
510
526
511 def _dirignore(self, f):
527 def _dirignore(self, f):
512 if f == '.':
528 if f == '.':
513 return False
529 return False
514 if self._ignore(f):
530 if self._ignore(f):
515 return True
531 return True
516 for p in _finddirs(f):
532 for p in _finddirs(f):
517 if self._ignore(p):
533 if self._ignore(p):
518 return True
534 return True
519 return False
535 return False
520
536
521 def walk(self, match, subrepos, unknown, ignored):
537 def walk(self, match, subrepos, unknown, ignored):
522 '''
538 '''
523 Walk recursively through the directory tree, finding all files
539 Walk recursively through the directory tree, finding all files
524 matched by match.
540 matched by match.
525
541
526 Return a dict mapping filename to stat-like object (either
542 Return a dict mapping filename to stat-like object (either
527 mercurial.osutil.stat instance or return value of os.stat()).
543 mercurial.osutil.stat instance or return value of os.stat()).
528 '''
544 '''
529
545
530 def fwarn(f, msg):
546 def fwarn(f, msg):
531 self._ui.warn('%s: %s\n' % (self.pathto(f), msg))
547 self._ui.warn('%s: %s\n' % (self.pathto(f), msg))
532 return False
548 return False
533
549
534 def badtype(mode):
550 def badtype(mode):
535 kind = _('unknown')
551 kind = _('unknown')
536 if stat.S_ISCHR(mode):
552 if stat.S_ISCHR(mode):
537 kind = _('character device')
553 kind = _('character device')
538 elif stat.S_ISBLK(mode):
554 elif stat.S_ISBLK(mode):
539 kind = _('block device')
555 kind = _('block device')
540 elif stat.S_ISFIFO(mode):
556 elif stat.S_ISFIFO(mode):
541 kind = _('fifo')
557 kind = _('fifo')
542 elif stat.S_ISSOCK(mode):
558 elif stat.S_ISSOCK(mode):
543 kind = _('socket')
559 kind = _('socket')
544 elif stat.S_ISDIR(mode):
560 elif stat.S_ISDIR(mode):
545 kind = _('directory')
561 kind = _('directory')
546 return _('unsupported file type (type is %s)') % kind
562 return _('unsupported file type (type is %s)') % kind
547
563
548 ignore = self._ignore
564 ignore = self._ignore
549 dirignore = self._dirignore
565 dirignore = self._dirignore
550 if ignored:
566 if ignored:
551 ignore = util.never
567 ignore = util.never
552 dirignore = util.never
568 dirignore = util.never
553 elif not unknown:
569 elif not unknown:
554 # if unknown and ignored are False, skip step 2
570 # if unknown and ignored are False, skip step 2
555 ignore = util.always
571 ignore = util.always
556 dirignore = util.always
572 dirignore = util.always
557
573
558 matchfn = match.matchfn
574 matchfn = match.matchfn
559 badfn = match.bad
575 badfn = match.bad
560 dmap = self._map
576 dmap = self._map
561 normpath = util.normpath
577 normpath = util.normpath
562 listdir = osutil.listdir
578 listdir = osutil.listdir
563 lstat = os.lstat
579 lstat = os.lstat
564 getkind = stat.S_IFMT
580 getkind = stat.S_IFMT
565 dirkind = stat.S_IFDIR
581 dirkind = stat.S_IFDIR
566 regkind = stat.S_IFREG
582 regkind = stat.S_IFREG
567 lnkkind = stat.S_IFLNK
583 lnkkind = stat.S_IFLNK
568 join = self._join
584 join = self._join
569 work = []
585 work = []
570 wadd = work.append
586 wadd = work.append
571
587
572 exact = skipstep3 = False
588 exact = skipstep3 = False
573 if matchfn == match.exact: # match.exact
589 if matchfn == match.exact: # match.exact
574 exact = True
590 exact = True
575 dirignore = util.always # skip step 2
591 dirignore = util.always # skip step 2
576 elif match.files() and not match.anypats(): # match.match, no patterns
592 elif match.files() and not match.anypats(): # match.match, no patterns
577 skipstep3 = True
593 skipstep3 = True
578
594
579 if not exact and self._checkcase:
595 if not exact and self._checkcase:
580 normalize = self._normalize
596 normalize = self._normalize
581 skipstep3 = False
597 skipstep3 = False
582 else:
598 else:
583 normalize = None
599 normalize = None
584
600
585 files = sorted(match.files())
601 files = sorted(match.files())
586 subrepos.sort()
602 subrepos.sort()
587 i, j = 0, 0
603 i, j = 0, 0
588 while i < len(files) and j < len(subrepos):
604 while i < len(files) and j < len(subrepos):
589 subpath = subrepos[j] + "/"
605 subpath = subrepos[j] + "/"
590 if files[i] < subpath:
606 if files[i] < subpath:
591 i += 1
607 i += 1
592 continue
608 continue
593 while i < len(files) and files[i].startswith(subpath):
609 while i < len(files) and files[i].startswith(subpath):
594 del files[i]
610 del files[i]
595 j += 1
611 j += 1
596
612
597 if not files or '.' in files:
613 if not files or '.' in files:
598 files = ['']
614 files = ['']
599 results = dict.fromkeys(subrepos)
615 results = dict.fromkeys(subrepos)
600 results['.hg'] = None
616 results['.hg'] = None
601
617
602 # step 1: find all explicit files
618 # step 1: find all explicit files
603 for ff in files:
619 for ff in files:
604 if normalize:
620 if normalize:
605 nf = normalize(normpath(ff), False, True)
621 nf = normalize(normpath(ff), False, True)
606 else:
622 else:
607 nf = normpath(ff)
623 nf = normpath(ff)
608 if nf in results:
624 if nf in results:
609 continue
625 continue
610
626
611 try:
627 try:
612 st = lstat(join(nf))
628 st = lstat(join(nf))
613 kind = getkind(st.st_mode)
629 kind = getkind(st.st_mode)
614 if kind == dirkind:
630 if kind == dirkind:
615 skipstep3 = False
631 skipstep3 = False
616 if nf in dmap:
632 if nf in dmap:
617 #file deleted on disk but still in dirstate
633 #file deleted on disk but still in dirstate
618 results[nf] = None
634 results[nf] = None
619 match.dir(nf)
635 match.dir(nf)
620 if not dirignore(nf):
636 if not dirignore(nf):
621 wadd(nf)
637 wadd(nf)
622 elif kind == regkind or kind == lnkkind:
638 elif kind == regkind or kind == lnkkind:
623 results[nf] = st
639 results[nf] = st
624 else:
640 else:
625 badfn(ff, badtype(kind))
641 badfn(ff, badtype(kind))
626 if nf in dmap:
642 if nf in dmap:
627 results[nf] = None
643 results[nf] = None
628 except OSError, inst:
644 except OSError, inst:
629 if nf in dmap: # does it exactly match a file?
645 if nf in dmap: # does it exactly match a file?
630 results[nf] = None
646 results[nf] = None
631 else: # does it match a directory?
647 else: # does it match a directory?
632 prefix = nf + "/"
648 prefix = nf + "/"
633 for fn in dmap:
649 for fn in dmap:
634 if fn.startswith(prefix):
650 if fn.startswith(prefix):
635 match.dir(nf)
651 match.dir(nf)
636 skipstep3 = False
652 skipstep3 = False
637 break
653 break
638 else:
654 else:
639 badfn(ff, inst.strerror)
655 badfn(ff, inst.strerror)
640
656
641 # step 2: visit subdirectories
657 # step 2: visit subdirectories
642 while work:
658 while work:
643 nd = work.pop()
659 nd = work.pop()
644 skip = None
660 skip = None
645 if nd == '.':
661 if nd == '.':
646 nd = ''
662 nd = ''
647 else:
663 else:
648 skip = '.hg'
664 skip = '.hg'
649 try:
665 try:
650 entries = listdir(join(nd), stat=True, skip=skip)
666 entries = listdir(join(nd), stat=True, skip=skip)
651 except OSError, inst:
667 except OSError, inst:
652 if inst.errno in (errno.EACCES, errno.ENOENT):
668 if inst.errno in (errno.EACCES, errno.ENOENT):
653 fwarn(nd, inst.strerror)
669 fwarn(nd, inst.strerror)
654 continue
670 continue
655 raise
671 raise
656 for f, kind, st in entries:
672 for f, kind, st in entries:
657 if normalize:
673 if normalize:
658 nf = normalize(nd and (nd + "/" + f) or f, True, True)
674 nf = normalize(nd and (nd + "/" + f) or f, True, True)
659 else:
675 else:
660 nf = nd and (nd + "/" + f) or f
676 nf = nd and (nd + "/" + f) or f
661 if nf not in results:
677 if nf not in results:
662 if kind == dirkind:
678 if kind == dirkind:
663 if not ignore(nf):
679 if not ignore(nf):
664 match.dir(nf)
680 match.dir(nf)
665 wadd(nf)
681 wadd(nf)
666 if nf in dmap and matchfn(nf):
682 if nf in dmap and matchfn(nf):
667 results[nf] = None
683 results[nf] = None
668 elif kind == regkind or kind == lnkkind:
684 elif kind == regkind or kind == lnkkind:
669 if nf in dmap:
685 if nf in dmap:
670 if matchfn(nf):
686 if matchfn(nf):
671 results[nf] = st
687 results[nf] = st
672 elif matchfn(nf) and not ignore(nf):
688 elif matchfn(nf) and not ignore(nf):
673 results[nf] = st
689 results[nf] = st
674 elif nf in dmap and matchfn(nf):
690 elif nf in dmap and matchfn(nf):
675 results[nf] = None
691 results[nf] = None
676
692
677 # step 3: report unseen items in the dmap hash
693 # step 3: report unseen items in the dmap hash
678 if not skipstep3 and not exact:
694 if not skipstep3 and not exact:
679 visit = sorted([f for f in dmap if f not in results and matchfn(f)])
695 visit = sorted([f for f in dmap if f not in results and matchfn(f)])
680 nf = iter(visit).next
696 nf = iter(visit).next
681 for st in util.statfiles([join(i) for i in visit]):
697 for st in util.statfiles([join(i) for i in visit]):
682 results[nf()] = st
698 results[nf()] = st
683 for s in subrepos:
699 for s in subrepos:
684 del results[s]
700 del results[s]
685 del results['.hg']
701 del results['.hg']
686 return results
702 return results
687
703
688 def status(self, match, subrepos, ignored, clean, unknown):
704 def status(self, match, subrepos, ignored, clean, unknown):
689 '''Determine the status of the working copy relative to the
705 '''Determine the status of the working copy relative to the
690 dirstate and return a tuple of lists (unsure, modified, added,
706 dirstate and return a tuple of lists (unsure, modified, added,
691 removed, deleted, unknown, ignored, clean), where:
707 removed, deleted, unknown, ignored, clean), where:
692
708
693 unsure:
709 unsure:
694 files that might have been modified since the dirstate was
710 files that might have been modified since the dirstate was
695 written, but need to be read to be sure (size is the same
711 written, but need to be read to be sure (size is the same
696 but mtime differs)
712 but mtime differs)
697 modified:
713 modified:
698 files that have definitely been modified since the dirstate
714 files that have definitely been modified since the dirstate
699 was written (different size or mode)
715 was written (different size or mode)
700 added:
716 added:
701 files that have been explicitly added with hg add
717 files that have been explicitly added with hg add
702 removed:
718 removed:
703 files that have been explicitly removed with hg remove
719 files that have been explicitly removed with hg remove
704 deleted:
720 deleted:
705 files that have been deleted through other means ("missing")
721 files that have been deleted through other means ("missing")
706 unknown:
722 unknown:
707 files not in the dirstate that are not ignored
723 files not in the dirstate that are not ignored
708 ignored:
724 ignored:
709 files not in the dirstate that are ignored
725 files not in the dirstate that are ignored
710 (by _dirignore())
726 (by _dirignore())
711 clean:
727 clean:
712 files that have definitely not been modified since the
728 files that have definitely not been modified since the
713 dirstate was written
729 dirstate was written
714 '''
730 '''
715 listignored, listclean, listunknown = ignored, clean, unknown
731 listignored, listclean, listunknown = ignored, clean, unknown
716 lookup, modified, added, unknown, ignored = [], [], [], [], []
732 lookup, modified, added, unknown, ignored = [], [], [], [], []
717 removed, deleted, clean = [], [], []
733 removed, deleted, clean = [], [], []
718
734
719 dmap = self._map
735 dmap = self._map
720 ladd = lookup.append # aka "unsure"
736 ladd = lookup.append # aka "unsure"
721 madd = modified.append
737 madd = modified.append
722 aadd = added.append
738 aadd = added.append
723 uadd = unknown.append
739 uadd = unknown.append
724 iadd = ignored.append
740 iadd = ignored.append
725 radd = removed.append
741 radd = removed.append
726 dadd = deleted.append
742 dadd = deleted.append
727 cadd = clean.append
743 cadd = clean.append
728 mexact = match.exact
744 mexact = match.exact
729 dirignore = self._dirignore
745 dirignore = self._dirignore
730 checkexec = self._checkexec
746 checkexec = self._checkexec
731 checklink = self._checklink
747 checklink = self._checklink
732 copymap = self._copymap
748 copymap = self._copymap
733 lastnormaltime = self._lastnormaltime
749 lastnormaltime = self._lastnormaltime
734
750
735 lnkkind = stat.S_IFLNK
751 lnkkind = stat.S_IFLNK
736
752
737 for fn, st in self.walk(match, subrepos, listunknown,
753 for fn, st in self.walk(match, subrepos, listunknown,
738 listignored).iteritems():
754 listignored).iteritems():
739 if fn not in dmap:
755 if fn not in dmap:
740 if (listignored or mexact(fn)) and dirignore(fn):
756 if (listignored or mexact(fn)) and dirignore(fn):
741 if listignored:
757 if listignored:
742 iadd(fn)
758 iadd(fn)
743 elif listunknown:
759 elif listunknown:
744 uadd(fn)
760 uadd(fn)
745 continue
761 continue
746
762
747 state, mode, size, time = dmap[fn]
763 state, mode, size, time = dmap[fn]
748
764
749 if not st and state in "nma":
765 if not st and state in "nma":
750 dadd(fn)
766 dadd(fn)
751 elif state == 'n':
767 elif state == 'n':
752 # The "mode & lnkkind != lnkkind or self._checklink"
768 # The "mode & lnkkind != lnkkind or self._checklink"
753 # lines are an expansion of "islink => checklink"
769 # lines are an expansion of "islink => checklink"
754 # where islink means "is this a link?" and checklink
770 # where islink means "is this a link?" and checklink
755 # means "can we check links?".
771 # means "can we check links?".
756 mtime = int(st.st_mtime)
772 mtime = int(st.st_mtime)
757 if (size >= 0 and
773 if (size >= 0 and
758 ((size != st.st_size and size != st.st_size & _rangemask)
774 ((size != st.st_size and size != st.st_size & _rangemask)
759 or ((mode ^ st.st_mode) & 0100 and checkexec))
775 or ((mode ^ st.st_mode) & 0100 and checkexec))
760 and (mode & lnkkind != lnkkind or checklink)
776 and (mode & lnkkind != lnkkind or checklink)
761 or size == -2 # other parent
777 or size == -2 # other parent
762 or fn in copymap):
778 or fn in copymap):
763 madd(fn)
779 madd(fn)
764 elif ((time != mtime and time != mtime & _rangemask)
780 elif ((time != mtime and time != mtime & _rangemask)
765 and (mode & lnkkind != lnkkind or checklink)):
781 and (mode & lnkkind != lnkkind or checklink)):
766 ladd(fn)
782 ladd(fn)
767 elif mtime == lastnormaltime:
783 elif mtime == lastnormaltime:
768 # fn may have been changed in the same timeslot without
784 # fn may have been changed in the same timeslot without
769 # changing its size. This can happen if we quickly do
785 # changing its size. This can happen if we quickly do
770 # multiple commits in a single transaction.
786 # multiple commits in a single transaction.
771 # Force lookup, so we don't miss such a racy file change.
787 # Force lookup, so we don't miss such a racy file change.
772 ladd(fn)
788 ladd(fn)
773 elif listclean:
789 elif listclean:
774 cadd(fn)
790 cadd(fn)
775 elif state == 'm':
791 elif state == 'm':
776 madd(fn)
792 madd(fn)
777 elif state == 'a':
793 elif state == 'a':
778 aadd(fn)
794 aadd(fn)
779 elif state == 'r':
795 elif state == 'r':
780 radd(fn)
796 radd(fn)
781
797
782 return (lookup, modified, added, removed, deleted, unknown, ignored,
798 return (lookup, modified, added, removed, deleted, unknown, ignored,
783 clean)
799 clean)
General Comments 0
You need to be logged in to leave comments. Login now