##// END OF EJS Templates
dirstate: fix generator/list error when using python 2.7...
Durham Goode -
r18663:05cf40f9 default
parent child Browse files
Show More
@@ -1,816 +1,820 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, gc
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 # Python's garbage collector triggers a GC each time a certain number
288 # Python's garbage collector triggers a GC each time a certain number
289 # of container objects (the number being defined by
289 # of container objects (the number being defined by
290 # gc.get_threshold()) are allocated. parse_dirstate creates a tuple
290 # gc.get_threshold()) are allocated. parse_dirstate creates a tuple
291 # for each file in the dirstate. The C version then immediately marks
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
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
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.
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,
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
296 # this can get very expensive. As a workaround, disable GC while
297 # parsing the dirstate.
297 # parsing the dirstate.
298 gcenabled = gc.isenabled()
298 gcenabled = gc.isenabled()
299 gc.disable()
299 gc.disable()
300 try:
300 try:
301 p = parsers.parse_dirstate(self._map, self._copymap, st)
301 p = parsers.parse_dirstate(self._map, self._copymap, st)
302 finally:
302 finally:
303 if gcenabled:
303 if gcenabled:
304 gc.enable()
304 gc.enable()
305 if not self._dirtypl:
305 if not self._dirtypl:
306 self._pl = p
306 self._pl = p
307
307
308 def invalidate(self):
308 def invalidate(self):
309 for a in ("_map", "_copymap", "_foldmap", "_branch", "_pl", "_dirs",
309 for a in ("_map", "_copymap", "_foldmap", "_branch", "_pl", "_dirs",
310 "_ignore"):
310 "_ignore"):
311 if a in self.__dict__:
311 if a in self.__dict__:
312 delattr(self, a)
312 delattr(self, a)
313 self._lastnormaltime = 0
313 self._lastnormaltime = 0
314 self._dirty = False
314 self._dirty = False
315
315
316 def copy(self, source, dest):
316 def copy(self, source, dest):
317 """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."""
318 if source == dest:
318 if source == dest:
319 return
319 return
320 self._dirty = True
320 self._dirty = True
321 if source is not None:
321 if source is not None:
322 self._copymap[dest] = source
322 self._copymap[dest] = source
323 elif dest in self._copymap:
323 elif dest in self._copymap:
324 del self._copymap[dest]
324 del self._copymap[dest]
325
325
326 def copied(self, file):
326 def copied(self, file):
327 return self._copymap.get(file, None)
327 return self._copymap.get(file, None)
328
328
329 def copies(self):
329 def copies(self):
330 return self._copymap
330 return self._copymap
331
331
332 def _droppath(self, f):
332 def _droppath(self, f):
333 if self[f] not in "?r" and "_dirs" in self.__dict__:
333 if self[f] not in "?r" and "_dirs" in self.__dict__:
334 _decdirs(self._dirs, f)
334 _decdirs(self._dirs, f)
335
335
336 def _addpath(self, f, state, mode, size, mtime):
336 def _addpath(self, f, state, mode, size, mtime):
337 oldstate = self[f]
337 oldstate = self[f]
338 if state == 'a' or oldstate == 'r':
338 if state == 'a' or oldstate == 'r':
339 scmutil.checkfilename(f)
339 scmutil.checkfilename(f)
340 if f in self._dirs:
340 if f in self._dirs:
341 raise util.Abort(_('directory %r already in dirstate') % f)
341 raise util.Abort(_('directory %r already in dirstate') % f)
342 # shadows
342 # shadows
343 for d in _finddirs(f):
343 for d in _finddirs(f):
344 if d in self._dirs:
344 if d in self._dirs:
345 break
345 break
346 if d in self._map and self[d] != 'r':
346 if d in self._map and self[d] != 'r':
347 raise util.Abort(
347 raise util.Abort(
348 _('file %r in dirstate clashes with %r') % (d, f))
348 _('file %r in dirstate clashes with %r') % (d, f))
349 if oldstate in "?r" and "_dirs" in self.__dict__:
349 if oldstate in "?r" and "_dirs" in self.__dict__:
350 _incdirs(self._dirs, f)
350 _incdirs(self._dirs, f)
351 self._dirty = True
351 self._dirty = True
352 self._map[f] = (state, mode, size, mtime)
352 self._map[f] = (state, mode, size, mtime)
353
353
354 def normal(self, f):
354 def normal(self, f):
355 '''Mark a file normal and clean.'''
355 '''Mark a file normal and clean.'''
356 s = os.lstat(self._join(f))
356 s = os.lstat(self._join(f))
357 mtime = int(s.st_mtime)
357 mtime = int(s.st_mtime)
358 self._addpath(f, 'n', s.st_mode,
358 self._addpath(f, 'n', s.st_mode,
359 s.st_size & _rangemask, mtime & _rangemask)
359 s.st_size & _rangemask, mtime & _rangemask)
360 if f in self._copymap:
360 if f in self._copymap:
361 del self._copymap[f]
361 del self._copymap[f]
362 if mtime > self._lastnormaltime:
362 if mtime > self._lastnormaltime:
363 # Remember the most recent modification timeslot for status(),
363 # Remember the most recent modification timeslot for status(),
364 # 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
365 # modifications that happen within the same timeslot.
365 # modifications that happen within the same timeslot.
366 self._lastnormaltime = mtime
366 self._lastnormaltime = mtime
367
367
368 def normallookup(self, f):
368 def normallookup(self, f):
369 '''Mark a file normal, but possibly dirty.'''
369 '''Mark a file normal, but possibly dirty.'''
370 if self._pl[1] != nullid and f in self._map:
370 if self._pl[1] != nullid and f in self._map:
371 # 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
372 # in state 'm' (-1) or coming from other parent (-2) before
372 # in state 'm' (-1) or coming from other parent (-2) before
373 # being removed, restore that state.
373 # being removed, restore that state.
374 entry = self._map[f]
374 entry = self._map[f]
375 if entry[0] == 'r' and entry[2] in (-1, -2):
375 if entry[0] == 'r' and entry[2] in (-1, -2):
376 source = self._copymap.get(f)
376 source = self._copymap.get(f)
377 if entry[2] == -1:
377 if entry[2] == -1:
378 self.merge(f)
378 self.merge(f)
379 elif entry[2] == -2:
379 elif entry[2] == -2:
380 self.otherparent(f)
380 self.otherparent(f)
381 if source:
381 if source:
382 self.copy(source, f)
382 self.copy(source, f)
383 return
383 return
384 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:
385 return
385 return
386 self._addpath(f, 'n', 0, -1, -1)
386 self._addpath(f, 'n', 0, -1, -1)
387 if f in self._copymap:
387 if f in self._copymap:
388 del self._copymap[f]
388 del self._copymap[f]
389
389
390 def otherparent(self, f):
390 def otherparent(self, f):
391 '''Mark as coming from the other parent, always dirty.'''
391 '''Mark as coming from the other parent, always dirty.'''
392 if self._pl[1] == nullid:
392 if self._pl[1] == nullid:
393 raise util.Abort(_("setting %r to other parent "
393 raise util.Abort(_("setting %r to other parent "
394 "only allowed in merges") % f)
394 "only allowed in merges") % f)
395 self._addpath(f, 'n', 0, -2, -1)
395 self._addpath(f, 'n', 0, -2, -1)
396 if f in self._copymap:
396 if f in self._copymap:
397 del self._copymap[f]
397 del self._copymap[f]
398
398
399 def add(self, f):
399 def add(self, f):
400 '''Mark a file added.'''
400 '''Mark a file added.'''
401 self._addpath(f, 'a', 0, -1, -1)
401 self._addpath(f, 'a', 0, -1, -1)
402 if f in self._copymap:
402 if f in self._copymap:
403 del self._copymap[f]
403 del self._copymap[f]
404
404
405 def remove(self, f):
405 def remove(self, f):
406 '''Mark a file removed.'''
406 '''Mark a file removed.'''
407 self._dirty = True
407 self._dirty = True
408 self._droppath(f)
408 self._droppath(f)
409 size = 0
409 size = 0
410 if self._pl[1] != nullid and f in self._map:
410 if self._pl[1] != nullid and f in self._map:
411 # backup the previous state
411 # backup the previous state
412 entry = self._map[f]
412 entry = self._map[f]
413 if entry[0] == 'm': # merge
413 if entry[0] == 'm': # merge
414 size = -1
414 size = -1
415 elif entry[0] == 'n' and entry[2] == -2: # other parent
415 elif entry[0] == 'n' and entry[2] == -2: # other parent
416 size = -2
416 size = -2
417 self._map[f] = ('r', 0, size, 0)
417 self._map[f] = ('r', 0, size, 0)
418 if size == 0 and f in self._copymap:
418 if size == 0 and f in self._copymap:
419 del self._copymap[f]
419 del self._copymap[f]
420
420
421 def merge(self, f):
421 def merge(self, f):
422 '''Mark a file merged.'''
422 '''Mark a file merged.'''
423 if self._pl[1] == nullid:
423 if self._pl[1] == nullid:
424 return self.normallookup(f)
424 return self.normallookup(f)
425 s = os.lstat(self._join(f))
425 s = os.lstat(self._join(f))
426 self._addpath(f, 'm', s.st_mode,
426 self._addpath(f, 'm', s.st_mode,
427 s.st_size & _rangemask, int(s.st_mtime) & _rangemask)
427 s.st_size & _rangemask, int(s.st_mtime) & _rangemask)
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 drop(self, f):
431 def drop(self, f):
432 '''Drop a file from the dirstate'''
432 '''Drop a file from the dirstate'''
433 if f in self._map:
433 if f in self._map:
434 self._dirty = True
434 self._dirty = True
435 self._droppath(f)
435 self._droppath(f)
436 del self._map[f]
436 del self._map[f]
437
437
438 def _normalize(self, path, isknown, ignoremissing=False, exists=None):
438 def _normalize(self, path, isknown, ignoremissing=False, exists=None):
439 normed = util.normcase(path)
439 normed = util.normcase(path)
440 folded = self._foldmap.get(normed, None)
440 folded = self._foldmap.get(normed, None)
441 if folded is None:
441 if folded is None:
442 if isknown:
442 if isknown:
443 folded = path
443 folded = path
444 else:
444 else:
445 if exists is None:
445 if exists is None:
446 exists = os.path.lexists(os.path.join(self._root, path))
446 exists = os.path.lexists(os.path.join(self._root, path))
447 if not exists:
447 if not exists:
448 # Maybe a path component exists
448 # Maybe a path component exists
449 if not ignoremissing and '/' in path:
449 if not ignoremissing and '/' in path:
450 d, f = path.rsplit('/', 1)
450 d, f = path.rsplit('/', 1)
451 d = self._normalize(d, isknown, ignoremissing, None)
451 d = self._normalize(d, isknown, ignoremissing, None)
452 folded = d + "/" + f
452 folded = d + "/" + f
453 else:
453 else:
454 # No path components, preserve original case
454 # No path components, preserve original case
455 folded = path
455 folded = path
456 else:
456 else:
457 # recursively normalize leading directory components
457 # recursively normalize leading directory components
458 # against dirstate
458 # against dirstate
459 if '/' in normed:
459 if '/' in normed:
460 d, f = normed.rsplit('/', 1)
460 d, f = normed.rsplit('/', 1)
461 d = self._normalize(d, isknown, ignoremissing, True)
461 d = self._normalize(d, isknown, ignoremissing, True)
462 r = self._root + "/" + d
462 r = self._root + "/" + d
463 folded = d + "/" + util.fspath(f, r)
463 folded = d + "/" + util.fspath(f, r)
464 else:
464 else:
465 folded = util.fspath(normed, self._root)
465 folded = util.fspath(normed, self._root)
466 self._foldmap[normed] = folded
466 self._foldmap[normed] = folded
467
467
468 return folded
468 return folded
469
469
470 def normalize(self, path, isknown=False, ignoremissing=False):
470 def normalize(self, path, isknown=False, ignoremissing=False):
471 '''
471 '''
472 normalize the case of a pathname when on a casefolding filesystem
472 normalize the case of a pathname when on a casefolding filesystem
473
473
474 isknown specifies whether the filename came from walking the
474 isknown specifies whether the filename came from walking the
475 disk, to avoid extra filesystem access.
475 disk, to avoid extra filesystem access.
476
476
477 If ignoremissing is True, missing path are returned
477 If ignoremissing is True, missing path are returned
478 unchanged. Otherwise, we try harder to normalize possibly
478 unchanged. Otherwise, we try harder to normalize possibly
479 existing path components.
479 existing path components.
480
480
481 The normalized case is determined based on the following precedence:
481 The normalized case is determined based on the following precedence:
482
482
483 - version of name already stored in the dirstate
483 - version of name already stored in the dirstate
484 - version of name stored on disk
484 - version of name stored on disk
485 - version provided via command arguments
485 - version provided via command arguments
486 '''
486 '''
487
487
488 if self._checkcase:
488 if self._checkcase:
489 return self._normalize(path, isknown, ignoremissing)
489 return self._normalize(path, isknown, ignoremissing)
490 return path
490 return path
491
491
492 def clear(self):
492 def clear(self):
493 self._map = {}
493 self._map = {}
494 if "_dirs" in self.__dict__:
494 if "_dirs" in self.__dict__:
495 delattr(self, "_dirs")
495 delattr(self, "_dirs")
496 self._copymap = {}
496 self._copymap = {}
497 self._pl = [nullid, nullid]
497 self._pl = [nullid, nullid]
498 self._lastnormaltime = 0
498 self._lastnormaltime = 0
499 self._dirty = True
499 self._dirty = True
500
500
501 def rebuild(self, parent, files):
501 def rebuild(self, parent, files):
502 self.clear()
502 self.clear()
503 for f in files:
503 for f in files:
504 if 'x' in files.flags(f):
504 if 'x' in files.flags(f):
505 self._map[f] = ('n', 0777, -1, 0)
505 self._map[f] = ('n', 0777, -1, 0)
506 else:
506 else:
507 self._map[f] = ('n', 0666, -1, 0)
507 self._map[f] = ('n', 0666, -1, 0)
508 self._pl = (parent, nullid)
508 self._pl = (parent, nullid)
509 self._dirty = True
509 self._dirty = True
510
510
511 def write(self):
511 def write(self):
512 if not self._dirty:
512 if not self._dirty:
513 return
513 return
514 st = self._opener("dirstate", "w", atomictemp=True)
514 st = self._opener("dirstate", "w", atomictemp=True)
515
515
516 def finish(s):
516 def finish(s):
517 st.write(s)
517 st.write(s)
518 st.close()
518 st.close()
519 self._lastnormaltime = 0
519 self._lastnormaltime = 0
520 self._dirty = self._dirtypl = False
520 self._dirty = self._dirtypl = False
521
521
522 # 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
523 # filesystem's notion of 'now'
523 # filesystem's notion of 'now'
524 now = util.fstat(st).st_mtime
524 now = util.fstat(st).st_mtime
525 finish(parsers.pack_dirstate(self._map, self._copymap, self._pl, now))
525 finish(parsers.pack_dirstate(self._map, self._copymap, self._pl, now))
526
526
527 def _dirignore(self, f):
527 def _dirignore(self, f):
528 if f == '.':
528 if f == '.':
529 return False
529 return False
530 if self._ignore(f):
530 if self._ignore(f):
531 return True
531 return True
532 for p in _finddirs(f):
532 for p in _finddirs(f):
533 if self._ignore(p):
533 if self._ignore(p):
534 return True
534 return True
535 return False
535 return False
536
536
537 def walk(self, match, subrepos, unknown, ignored):
537 def walk(self, match, subrepos, unknown, ignored):
538 '''
538 '''
539 Walk recursively through the directory tree, finding all files
539 Walk recursively through the directory tree, finding all files
540 matched by match.
540 matched by match.
541
541
542 Return a dict mapping filename to stat-like object (either
542 Return a dict mapping filename to stat-like object (either
543 mercurial.osutil.stat instance or return value of os.stat()).
543 mercurial.osutil.stat instance or return value of os.stat()).
544 '''
544 '''
545
545
546 def fwarn(f, msg):
546 def fwarn(f, msg):
547 self._ui.warn('%s: %s\n' % (self.pathto(f), msg))
547 self._ui.warn('%s: %s\n' % (self.pathto(f), msg))
548 return False
548 return False
549
549
550 def badtype(mode):
550 def badtype(mode):
551 kind = _('unknown')
551 kind = _('unknown')
552 if stat.S_ISCHR(mode):
552 if stat.S_ISCHR(mode):
553 kind = _('character device')
553 kind = _('character device')
554 elif stat.S_ISBLK(mode):
554 elif stat.S_ISBLK(mode):
555 kind = _('block device')
555 kind = _('block device')
556 elif stat.S_ISFIFO(mode):
556 elif stat.S_ISFIFO(mode):
557 kind = _('fifo')
557 kind = _('fifo')
558 elif stat.S_ISSOCK(mode):
558 elif stat.S_ISSOCK(mode):
559 kind = _('socket')
559 kind = _('socket')
560 elif stat.S_ISDIR(mode):
560 elif stat.S_ISDIR(mode):
561 kind = _('directory')
561 kind = _('directory')
562 return _('unsupported file type (type is %s)') % kind
562 return _('unsupported file type (type is %s)') % kind
563
563
564 ignore = self._ignore
564 ignore = self._ignore
565 dirignore = self._dirignore
565 dirignore = self._dirignore
566 if ignored:
566 if ignored:
567 ignore = util.never
567 ignore = util.never
568 dirignore = util.never
568 dirignore = util.never
569 elif not unknown:
569 elif not unknown:
570 # if unknown and ignored are False, skip step 2
570 # if unknown and ignored are False, skip step 2
571 ignore = util.always
571 ignore = util.always
572 dirignore = util.always
572 dirignore = util.always
573
573
574 matchfn = match.matchfn
574 matchfn = match.matchfn
575 badfn = match.bad
575 badfn = match.bad
576 dmap = self._map
576 dmap = self._map
577 normpath = util.normpath
577 normpath = util.normpath
578 listdir = osutil.listdir
578 listdir = osutil.listdir
579 lstat = os.lstat
579 lstat = os.lstat
580 getkind = stat.S_IFMT
580 getkind = stat.S_IFMT
581 dirkind = stat.S_IFDIR
581 dirkind = stat.S_IFDIR
582 regkind = stat.S_IFREG
582 regkind = stat.S_IFREG
583 lnkkind = stat.S_IFLNK
583 lnkkind = stat.S_IFLNK
584 join = self._join
584 join = self._join
585 work = []
585 work = []
586 wadd = work.append
586 wadd = work.append
587
587
588 exact = skipstep3 = False
588 exact = skipstep3 = False
589 if matchfn == match.exact: # match.exact
589 if matchfn == match.exact: # match.exact
590 exact = True
590 exact = True
591 dirignore = util.always # skip step 2
591 dirignore = util.always # skip step 2
592 elif match.files() and not match.anypats(): # match.match, no patterns
592 elif match.files() and not match.anypats(): # match.match, no patterns
593 skipstep3 = True
593 skipstep3 = True
594
594
595 if not exact and self._checkcase:
595 if not exact and self._checkcase:
596 normalize = self._normalize
596 normalize = self._normalize
597 skipstep3 = False
597 skipstep3 = False
598 else:
598 else:
599 normalize = None
599 normalize = None
600
600
601 files = sorted(match.files())
601 files = sorted(match.files())
602 subrepos.sort()
602 subrepos.sort()
603 i, j = 0, 0
603 i, j = 0, 0
604 while i < len(files) and j < len(subrepos):
604 while i < len(files) and j < len(subrepos):
605 subpath = subrepos[j] + "/"
605 subpath = subrepos[j] + "/"
606 if files[i] < subpath:
606 if files[i] < subpath:
607 i += 1
607 i += 1
608 continue
608 continue
609 while i < len(files) and files[i].startswith(subpath):
609 while i < len(files) and files[i].startswith(subpath):
610 del files[i]
610 del files[i]
611 j += 1
611 j += 1
612
612
613 if not files or '.' in files:
613 if not files or '.' in files:
614 files = ['']
614 files = ['']
615 results = dict.fromkeys(subrepos)
615 results = dict.fromkeys(subrepos)
616 results['.hg'] = None
616 results['.hg'] = None
617
617
618 # step 1: find all explicit files
618 # step 1: find all explicit files
619 for ff in files:
619 for ff in files:
620 if normalize:
620 if normalize:
621 nf = normalize(normpath(ff), False, True)
621 nf = normalize(normpath(ff), False, True)
622 else:
622 else:
623 nf = normpath(ff)
623 nf = normpath(ff)
624 if nf in results:
624 if nf in results:
625 continue
625 continue
626
626
627 try:
627 try:
628 st = lstat(join(nf))
628 st = lstat(join(nf))
629 kind = getkind(st.st_mode)
629 kind = getkind(st.st_mode)
630 if kind == dirkind:
630 if kind == dirkind:
631 skipstep3 = False
631 skipstep3 = False
632 if nf in dmap:
632 if nf in dmap:
633 #file deleted on disk but still in dirstate
633 #file deleted on disk but still in dirstate
634 results[nf] = None
634 results[nf] = None
635 match.dir(nf)
635 match.dir(nf)
636 if not dirignore(nf):
636 if not dirignore(nf):
637 wadd(nf)
637 wadd(nf)
638 elif kind == regkind or kind == lnkkind:
638 elif kind == regkind or kind == lnkkind:
639 results[nf] = st
639 results[nf] = st
640 else:
640 else:
641 badfn(ff, badtype(kind))
641 badfn(ff, badtype(kind))
642 if nf in dmap:
642 if nf in dmap:
643 results[nf] = None
643 results[nf] = None
644 except OSError, inst:
644 except OSError, inst:
645 if nf in dmap: # does it exactly match a file?
645 if nf in dmap: # does it exactly match a file?
646 results[nf] = None
646 results[nf] = None
647 else: # does it match a directory?
647 else: # does it match a directory?
648 prefix = nf + "/"
648 prefix = nf + "/"
649 for fn in dmap:
649 for fn in dmap:
650 if fn.startswith(prefix):
650 if fn.startswith(prefix):
651 match.dir(nf)
651 match.dir(nf)
652 skipstep3 = False
652 skipstep3 = False
653 break
653 break
654 else:
654 else:
655 badfn(ff, inst.strerror)
655 badfn(ff, inst.strerror)
656
656
657 # step 2: visit subdirectories
657 # step 2: visit subdirectories
658 while work:
658 while work:
659 nd = work.pop()
659 nd = work.pop()
660 skip = None
660 skip = None
661 if nd == '.':
661 if nd == '.':
662 nd = ''
662 nd = ''
663 else:
663 else:
664 skip = '.hg'
664 skip = '.hg'
665 try:
665 try:
666 entries = listdir(join(nd), stat=True, skip=skip)
666 entries = listdir(join(nd), stat=True, skip=skip)
667 except OSError, inst:
667 except OSError, inst:
668 if inst.errno in (errno.EACCES, errno.ENOENT):
668 if inst.errno in (errno.EACCES, errno.ENOENT):
669 fwarn(nd, inst.strerror)
669 fwarn(nd, inst.strerror)
670 continue
670 continue
671 raise
671 raise
672 for f, kind, st in entries:
672 for f, kind, st in entries:
673 if normalize:
673 if normalize:
674 nf = normalize(nd and (nd + "/" + f) or f, True, True)
674 nf = normalize(nd and (nd + "/" + f) or f, True, True)
675 else:
675 else:
676 nf = nd and (nd + "/" + f) or f
676 nf = nd and (nd + "/" + f) or f
677 if nf not in results:
677 if nf not in results:
678 if kind == dirkind:
678 if kind == dirkind:
679 if not ignore(nf):
679 if not ignore(nf):
680 match.dir(nf)
680 match.dir(nf)
681 wadd(nf)
681 wadd(nf)
682 if nf in dmap and matchfn(nf):
682 if nf in dmap and matchfn(nf):
683 results[nf] = None
683 results[nf] = None
684 elif kind == regkind or kind == lnkkind:
684 elif kind == regkind or kind == lnkkind:
685 if nf in dmap:
685 if nf in dmap:
686 if matchfn(nf):
686 if matchfn(nf):
687 results[nf] = st
687 results[nf] = st
688 elif matchfn(nf) and not ignore(nf):
688 elif matchfn(nf) and not ignore(nf):
689 results[nf] = st
689 results[nf] = st
690 elif nf in dmap and matchfn(nf):
690 elif nf in dmap and matchfn(nf):
691 results[nf] = None
691 results[nf] = None
692
692
693 # step 3: report unseen items in the dmap hash
693 # step 3: report unseen items in the dmap hash
694 if not skipstep3 and not exact:
694 if not skipstep3 and not exact:
695 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)])
696 if unknown:
696 if unknown:
697 # unknown == True means we walked the full directory tree above.
697 # unknown == True means we walked the full directory tree above.
698 # So if a file is not seen it was either a) not matching matchfn
698 # So if a file is not seen it was either a) not matching matchfn
699 # b) ignored, c) missing, or d) under a symlink directory.
699 # b) ignored, c) missing, or d) under a symlink directory.
700 audit_path = scmutil.pathauditor(self._root)
700 audit_path = scmutil.pathauditor(self._root)
701
701
702 for nf in iter(visit):
702 for nf in iter(visit):
703 # Report ignored items in the dmap as long as they are not
703 # Report ignored items in the dmap as long as they are not
704 # under a symlink directory.
704 # under a symlink directory.
705 if ignore(nf) and audit_path.check(nf):
705 if ignore(nf) and audit_path.check(nf):
706 results[nf] = util.statfiles([join(nf)])[0]
706 try:
707 results[nf] = lstat(join(nf))
708 except OSError:
709 # file doesn't exist
710 results[nf] = None
707 else:
711 else:
708 # It's either missing or under a symlink directory
712 # It's either missing or under a symlink directory
709 results[nf] = None
713 results[nf] = None
710 else:
714 else:
711 # We may not have walked the full directory tree above,
715 # We may not have walked the full directory tree above,
712 # so stat everything we missed.
716 # so stat everything we missed.
713 nf = iter(visit).next
717 nf = iter(visit).next
714 for st in util.statfiles([join(i) for i in visit]):
718 for st in util.statfiles([join(i) for i in visit]):
715 results[nf()] = st
719 results[nf()] = st
716 for s in subrepos:
720 for s in subrepos:
717 del results[s]
721 del results[s]
718 del results['.hg']
722 del results['.hg']
719 return results
723 return results
720
724
721 def status(self, match, subrepos, ignored, clean, unknown):
725 def status(self, match, subrepos, ignored, clean, unknown):
722 '''Determine the status of the working copy relative to the
726 '''Determine the status of the working copy relative to the
723 dirstate and return a tuple of lists (unsure, modified, added,
727 dirstate and return a tuple of lists (unsure, modified, added,
724 removed, deleted, unknown, ignored, clean), where:
728 removed, deleted, unknown, ignored, clean), where:
725
729
726 unsure:
730 unsure:
727 files that might have been modified since the dirstate was
731 files that might have been modified since the dirstate was
728 written, but need to be read to be sure (size is the same
732 written, but need to be read to be sure (size is the same
729 but mtime differs)
733 but mtime differs)
730 modified:
734 modified:
731 files that have definitely been modified since the dirstate
735 files that have definitely been modified since the dirstate
732 was written (different size or mode)
736 was written (different size or mode)
733 added:
737 added:
734 files that have been explicitly added with hg add
738 files that have been explicitly added with hg add
735 removed:
739 removed:
736 files that have been explicitly removed with hg remove
740 files that have been explicitly removed with hg remove
737 deleted:
741 deleted:
738 files that have been deleted through other means ("missing")
742 files that have been deleted through other means ("missing")
739 unknown:
743 unknown:
740 files not in the dirstate that are not ignored
744 files not in the dirstate that are not ignored
741 ignored:
745 ignored:
742 files not in the dirstate that are ignored
746 files not in the dirstate that are ignored
743 (by _dirignore())
747 (by _dirignore())
744 clean:
748 clean:
745 files that have definitely not been modified since the
749 files that have definitely not been modified since the
746 dirstate was written
750 dirstate was written
747 '''
751 '''
748 listignored, listclean, listunknown = ignored, clean, unknown
752 listignored, listclean, listunknown = ignored, clean, unknown
749 lookup, modified, added, unknown, ignored = [], [], [], [], []
753 lookup, modified, added, unknown, ignored = [], [], [], [], []
750 removed, deleted, clean = [], [], []
754 removed, deleted, clean = [], [], []
751
755
752 dmap = self._map
756 dmap = self._map
753 ladd = lookup.append # aka "unsure"
757 ladd = lookup.append # aka "unsure"
754 madd = modified.append
758 madd = modified.append
755 aadd = added.append
759 aadd = added.append
756 uadd = unknown.append
760 uadd = unknown.append
757 iadd = ignored.append
761 iadd = ignored.append
758 radd = removed.append
762 radd = removed.append
759 dadd = deleted.append
763 dadd = deleted.append
760 cadd = clean.append
764 cadd = clean.append
761 mexact = match.exact
765 mexact = match.exact
762 dirignore = self._dirignore
766 dirignore = self._dirignore
763 checkexec = self._checkexec
767 checkexec = self._checkexec
764 checklink = self._checklink
768 checklink = self._checklink
765 copymap = self._copymap
769 copymap = self._copymap
766 lastnormaltime = self._lastnormaltime
770 lastnormaltime = self._lastnormaltime
767
771
768 lnkkind = stat.S_IFLNK
772 lnkkind = stat.S_IFLNK
769
773
770 for fn, st in self.walk(match, subrepos, listunknown,
774 for fn, st in self.walk(match, subrepos, listunknown,
771 listignored).iteritems():
775 listignored).iteritems():
772 if fn not in dmap:
776 if fn not in dmap:
773 if (listignored or mexact(fn)) and dirignore(fn):
777 if (listignored or mexact(fn)) and dirignore(fn):
774 if listignored:
778 if listignored:
775 iadd(fn)
779 iadd(fn)
776 elif listunknown:
780 elif listunknown:
777 uadd(fn)
781 uadd(fn)
778 continue
782 continue
779
783
780 state, mode, size, time = dmap[fn]
784 state, mode, size, time = dmap[fn]
781
785
782 if not st and state in "nma":
786 if not st and state in "nma":
783 dadd(fn)
787 dadd(fn)
784 elif state == 'n':
788 elif state == 'n':
785 # The "mode & lnkkind != lnkkind or self._checklink"
789 # The "mode & lnkkind != lnkkind or self._checklink"
786 # lines are an expansion of "islink => checklink"
790 # lines are an expansion of "islink => checklink"
787 # where islink means "is this a link?" and checklink
791 # where islink means "is this a link?" and checklink
788 # means "can we check links?".
792 # means "can we check links?".
789 mtime = int(st.st_mtime)
793 mtime = int(st.st_mtime)
790 if (size >= 0 and
794 if (size >= 0 and
791 ((size != st.st_size and size != st.st_size & _rangemask)
795 ((size != st.st_size and size != st.st_size & _rangemask)
792 or ((mode ^ st.st_mode) & 0100 and checkexec))
796 or ((mode ^ st.st_mode) & 0100 and checkexec))
793 and (mode & lnkkind != lnkkind or checklink)
797 and (mode & lnkkind != lnkkind or checklink)
794 or size == -2 # other parent
798 or size == -2 # other parent
795 or fn in copymap):
799 or fn in copymap):
796 madd(fn)
800 madd(fn)
797 elif ((time != mtime and time != mtime & _rangemask)
801 elif ((time != mtime and time != mtime & _rangemask)
798 and (mode & lnkkind != lnkkind or checklink)):
802 and (mode & lnkkind != lnkkind or checklink)):
799 ladd(fn)
803 ladd(fn)
800 elif mtime == lastnormaltime:
804 elif mtime == lastnormaltime:
801 # fn may have been changed in the same timeslot without
805 # fn may have been changed in the same timeslot without
802 # changing its size. This can happen if we quickly do
806 # changing its size. This can happen if we quickly do
803 # multiple commits in a single transaction.
807 # multiple commits in a single transaction.
804 # Force lookup, so we don't miss such a racy file change.
808 # Force lookup, so we don't miss such a racy file change.
805 ladd(fn)
809 ladd(fn)
806 elif listclean:
810 elif listclean:
807 cadd(fn)
811 cadd(fn)
808 elif state == 'm':
812 elif state == 'm':
809 madd(fn)
813 madd(fn)
810 elif state == 'a':
814 elif state == 'a':
811 aadd(fn)
815 aadd(fn)
812 elif state == 'r':
816 elif state == 'r':
813 radd(fn)
817 radd(fn)
814
818
815 return (lookup, modified, added, removed, deleted, unknown, ignored,
819 return (lookup, modified, added, removed, deleted, unknown, ignored,
816 clean)
820 clean)
General Comments 0
You need to be logged in to leave comments. Login now