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