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