##// END OF EJS Templates
dirstate: inline more properties and methods in status...
Siddharth Agarwal -
r18034:1a570f04 default
parent child Browse files
Show More
@@ -1,802 +1,808 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 finally:
267 finally:
268 f.close()
268 f.close()
269
269
270 def _read(self):
270 def _read(self):
271 self._map = {}
271 self._map = {}
272 self._copymap = {}
272 self._copymap = {}
273 try:
273 try:
274 st = self._opener.read("dirstate")
274 st = self._opener.read("dirstate")
275 except IOError, err:
275 except IOError, err:
276 if err.errno != errno.ENOENT:
276 if err.errno != errno.ENOENT:
277 raise
277 raise
278 return
278 return
279 if not st:
279 if not st:
280 return
280 return
281
281
282 p = parsers.parse_dirstate(self._map, self._copymap, st)
282 p = parsers.parse_dirstate(self._map, self._copymap, st)
283 if not self._dirtypl:
283 if not self._dirtypl:
284 self._pl = p
284 self._pl = p
285
285
286 def invalidate(self):
286 def invalidate(self):
287 for a in ("_map", "_copymap", "_foldmap", "_branch", "_pl", "_dirs",
287 for a in ("_map", "_copymap", "_foldmap", "_branch", "_pl", "_dirs",
288 "_ignore"):
288 "_ignore"):
289 if a in self.__dict__:
289 if a in self.__dict__:
290 delattr(self, a)
290 delattr(self, a)
291 self._lastnormaltime = 0
291 self._lastnormaltime = 0
292 self._dirty = False
292 self._dirty = False
293
293
294 def copy(self, source, dest):
294 def copy(self, source, dest):
295 """Mark dest as a copy of source. Unmark dest if source is None."""
295 """Mark dest as a copy of source. Unmark dest if source is None."""
296 if source == dest:
296 if source == dest:
297 return
297 return
298 self._dirty = True
298 self._dirty = True
299 if source is not None:
299 if source is not None:
300 self._copymap[dest] = source
300 self._copymap[dest] = source
301 elif dest in self._copymap:
301 elif dest in self._copymap:
302 del self._copymap[dest]
302 del self._copymap[dest]
303
303
304 def copied(self, file):
304 def copied(self, file):
305 return self._copymap.get(file, None)
305 return self._copymap.get(file, None)
306
306
307 def copies(self):
307 def copies(self):
308 return self._copymap
308 return self._copymap
309
309
310 def _droppath(self, f):
310 def _droppath(self, f):
311 if self[f] not in "?r" and "_dirs" in self.__dict__:
311 if self[f] not in "?r" and "_dirs" in self.__dict__:
312 _decdirs(self._dirs, f)
312 _decdirs(self._dirs, f)
313
313
314 def _addpath(self, f, state, mode, size, mtime):
314 def _addpath(self, f, state, mode, size, mtime):
315 oldstate = self[f]
315 oldstate = self[f]
316 if state == 'a' or oldstate == 'r':
316 if state == 'a' or oldstate == 'r':
317 scmutil.checkfilename(f)
317 scmutil.checkfilename(f)
318 if f in self._dirs:
318 if f in self._dirs:
319 raise util.Abort(_('directory %r already in dirstate') % f)
319 raise util.Abort(_('directory %r already in dirstate') % f)
320 # shadows
320 # shadows
321 for d in _finddirs(f):
321 for d in _finddirs(f):
322 if d in self._dirs:
322 if d in self._dirs:
323 break
323 break
324 if d in self._map and self[d] != 'r':
324 if d in self._map and self[d] != 'r':
325 raise util.Abort(
325 raise util.Abort(
326 _('file %r in dirstate clashes with %r') % (d, f))
326 _('file %r in dirstate clashes with %r') % (d, f))
327 if oldstate in "?r" and "_dirs" in self.__dict__:
327 if oldstate in "?r" and "_dirs" in self.__dict__:
328 _incdirs(self._dirs, f)
328 _incdirs(self._dirs, f)
329 self._dirty = True
329 self._dirty = True
330 self._map[f] = (state, mode, size, mtime)
330 self._map[f] = (state, mode, size, mtime)
331
331
332 def normal(self, f):
332 def normal(self, f):
333 '''Mark a file normal and clean.'''
333 '''Mark a file normal and clean.'''
334 s = os.lstat(self._join(f))
334 s = os.lstat(self._join(f))
335 mtime = int(s.st_mtime)
335 mtime = int(s.st_mtime)
336 self._addpath(f, 'n', s.st_mode,
336 self._addpath(f, 'n', s.st_mode,
337 s.st_size & _rangemask, mtime & _rangemask)
337 s.st_size & _rangemask, mtime & _rangemask)
338 if f in self._copymap:
338 if f in self._copymap:
339 del self._copymap[f]
339 del self._copymap[f]
340 if mtime > self._lastnormaltime:
340 if mtime > self._lastnormaltime:
341 # Remember the most recent modification timeslot for status(),
341 # Remember the most recent modification timeslot for status(),
342 # to make sure we won't miss future size-preserving file content
342 # to make sure we won't miss future size-preserving file content
343 # modifications that happen within the same timeslot.
343 # modifications that happen within the same timeslot.
344 self._lastnormaltime = mtime
344 self._lastnormaltime = mtime
345
345
346 def normallookup(self, f):
346 def normallookup(self, f):
347 '''Mark a file normal, but possibly dirty.'''
347 '''Mark a file normal, but possibly dirty.'''
348 if self._pl[1] != nullid and f in self._map:
348 if self._pl[1] != nullid and f in self._map:
349 # if there is a merge going on and the file was either
349 # if there is a merge going on and the file was either
350 # in state 'm' (-1) or coming from other parent (-2) before
350 # in state 'm' (-1) or coming from other parent (-2) before
351 # being removed, restore that state.
351 # being removed, restore that state.
352 entry = self._map[f]
352 entry = self._map[f]
353 if entry[0] == 'r' and entry[2] in (-1, -2):
353 if entry[0] == 'r' and entry[2] in (-1, -2):
354 source = self._copymap.get(f)
354 source = self._copymap.get(f)
355 if entry[2] == -1:
355 if entry[2] == -1:
356 self.merge(f)
356 self.merge(f)
357 elif entry[2] == -2:
357 elif entry[2] == -2:
358 self.otherparent(f)
358 self.otherparent(f)
359 if source:
359 if source:
360 self.copy(source, f)
360 self.copy(source, f)
361 return
361 return
362 if entry[0] == 'm' or entry[0] == 'n' and entry[2] == -2:
362 if entry[0] == 'm' or entry[0] == 'n' and entry[2] == -2:
363 return
363 return
364 self._addpath(f, 'n', 0, -1, -1)
364 self._addpath(f, 'n', 0, -1, -1)
365 if f in self._copymap:
365 if f in self._copymap:
366 del self._copymap[f]
366 del self._copymap[f]
367
367
368 def otherparent(self, f):
368 def otherparent(self, f):
369 '''Mark as coming from the other parent, always dirty.'''
369 '''Mark as coming from the other parent, always dirty.'''
370 if self._pl[1] == nullid:
370 if self._pl[1] == nullid:
371 raise util.Abort(_("setting %r to other parent "
371 raise util.Abort(_("setting %r to other parent "
372 "only allowed in merges") % f)
372 "only allowed in merges") % f)
373 self._addpath(f, 'n', 0, -2, -1)
373 self._addpath(f, 'n', 0, -2, -1)
374 if f in self._copymap:
374 if f in self._copymap:
375 del self._copymap[f]
375 del self._copymap[f]
376
376
377 def add(self, f):
377 def add(self, f):
378 '''Mark a file added.'''
378 '''Mark a file added.'''
379 self._addpath(f, 'a', 0, -1, -1)
379 self._addpath(f, 'a', 0, -1, -1)
380 if f in self._copymap:
380 if f in self._copymap:
381 del self._copymap[f]
381 del self._copymap[f]
382
382
383 def remove(self, f):
383 def remove(self, f):
384 '''Mark a file removed.'''
384 '''Mark a file removed.'''
385 self._dirty = True
385 self._dirty = True
386 self._droppath(f)
386 self._droppath(f)
387 size = 0
387 size = 0
388 if self._pl[1] != nullid and f in self._map:
388 if self._pl[1] != nullid and f in self._map:
389 # backup the previous state
389 # backup the previous state
390 entry = self._map[f]
390 entry = self._map[f]
391 if entry[0] == 'm': # merge
391 if entry[0] == 'm': # merge
392 size = -1
392 size = -1
393 elif entry[0] == 'n' and entry[2] == -2: # other parent
393 elif entry[0] == 'n' and entry[2] == -2: # other parent
394 size = -2
394 size = -2
395 self._map[f] = ('r', 0, size, 0)
395 self._map[f] = ('r', 0, size, 0)
396 if size == 0 and f in self._copymap:
396 if size == 0 and f in self._copymap:
397 del self._copymap[f]
397 del self._copymap[f]
398
398
399 def merge(self, f):
399 def merge(self, f):
400 '''Mark a file merged.'''
400 '''Mark a file merged.'''
401 if self._pl[1] == nullid:
401 if self._pl[1] == nullid:
402 return self.normallookup(f)
402 return self.normallookup(f)
403 s = os.lstat(self._join(f))
403 s = os.lstat(self._join(f))
404 self._addpath(f, 'm', s.st_mode,
404 self._addpath(f, 'm', s.st_mode,
405 s.st_size & _rangemask, int(s.st_mtime) & _rangemask)
405 s.st_size & _rangemask, int(s.st_mtime) & _rangemask)
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 = None
608 normalize = None
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 if normalize:
629 if normalize:
630 nf = normalize(normpath(ff), False, True)
630 nf = normalize(normpath(ff), False, True)
631 else:
631 else:
632 nf = normpath(ff)
632 nf = normpath(ff)
633 if nf in results:
633 if nf in results:
634 continue
634 continue
635
635
636 try:
636 try:
637 st = lstat(join(nf))
637 st = lstat(join(nf))
638 kind = getkind(st.st_mode)
638 kind = getkind(st.st_mode)
639 if kind == dirkind:
639 if kind == dirkind:
640 skipstep3 = False
640 skipstep3 = False
641 if nf in dmap:
641 if nf in dmap:
642 #file deleted on disk but still in dirstate
642 #file deleted on disk but still in dirstate
643 results[nf] = None
643 results[nf] = None
644 match.dir(nf)
644 match.dir(nf)
645 if not dirignore(nf):
645 if not dirignore(nf):
646 wadd(nf)
646 wadd(nf)
647 elif kind == regkind or kind == lnkkind:
647 elif kind == regkind or kind == lnkkind:
648 results[nf] = st
648 results[nf] = st
649 else:
649 else:
650 badfn(ff, badtype(kind))
650 badfn(ff, badtype(kind))
651 if nf in dmap:
651 if nf in dmap:
652 results[nf] = None
652 results[nf] = None
653 except OSError, inst:
653 except OSError, inst:
654 if nf in dmap: # does it exactly match a file?
654 if nf in dmap: # does it exactly match a file?
655 results[nf] = None
655 results[nf] = None
656 else: # does it match a directory?
656 else: # does it match a directory?
657 prefix = nf + "/"
657 prefix = nf + "/"
658 for fn in dmap:
658 for fn in dmap:
659 if fn.startswith(prefix):
659 if fn.startswith(prefix):
660 match.dir(nf)
660 match.dir(nf)
661 skipstep3 = False
661 skipstep3 = False
662 break
662 break
663 else:
663 else:
664 badfn(ff, inst.strerror)
664 badfn(ff, inst.strerror)
665
665
666 # step 2: visit subdirectories
666 # step 2: visit subdirectories
667 while work:
667 while work:
668 nd = work.pop()
668 nd = work.pop()
669 skip = None
669 skip = None
670 if nd == '.':
670 if nd == '.':
671 nd = ''
671 nd = ''
672 else:
672 else:
673 skip = '.hg'
673 skip = '.hg'
674 try:
674 try:
675 entries = listdir(join(nd), stat=True, skip=skip)
675 entries = listdir(join(nd), stat=True, skip=skip)
676 except OSError, inst:
676 except OSError, inst:
677 if inst.errno in (errno.EACCES, errno.ENOENT):
677 if inst.errno in (errno.EACCES, errno.ENOENT):
678 fwarn(nd, inst.strerror)
678 fwarn(nd, inst.strerror)
679 continue
679 continue
680 raise
680 raise
681 for f, kind, st in entries:
681 for f, kind, st in entries:
682 if normalize:
682 if normalize:
683 nf = normalize(nd and (nd + "/" + f) or f, True, True)
683 nf = normalize(nd and (nd + "/" + f) or f, True, True)
684 else:
684 else:
685 nf = nd and (nd + "/" + f) or f
685 nf = nd and (nd + "/" + f) or f
686 if nf not in results:
686 if nf not in results:
687 if kind == dirkind:
687 if kind == dirkind:
688 if not ignore(nf):
688 if not ignore(nf):
689 match.dir(nf)
689 match.dir(nf)
690 wadd(nf)
690 wadd(nf)
691 if nf in dmap and matchfn(nf):
691 if nf in dmap and matchfn(nf):
692 results[nf] = None
692 results[nf] = None
693 elif kind == regkind or kind == lnkkind:
693 elif kind == regkind or kind == lnkkind:
694 if nf in dmap:
694 if nf in dmap:
695 if matchfn(nf):
695 if matchfn(nf):
696 results[nf] = st
696 results[nf] = st
697 elif matchfn(nf) and not ignore(nf):
697 elif matchfn(nf) and not ignore(nf):
698 results[nf] = st
698 results[nf] = st
699 elif nf in dmap and matchfn(nf):
699 elif nf in dmap and matchfn(nf):
700 results[nf] = None
700 results[nf] = None
701
701
702 # step 3: report unseen items in the dmap hash
702 # step 3: report unseen items in the dmap hash
703 if not skipstep3 and not exact:
703 if not skipstep3 and not exact:
704 visit = sorted([f for f in dmap if f not in results and matchfn(f)])
704 visit = sorted([f for f in dmap if f not in results and matchfn(f)])
705 nf = iter(visit).next
705 nf = iter(visit).next
706 for st in util.statfiles([join(i) for i in visit]):
706 for st in util.statfiles([join(i) for i in visit]):
707 results[nf()] = st
707 results[nf()] = st
708 for s in subrepos:
708 for s in subrepos:
709 del results[s]
709 del results[s]
710 del results['.hg']
710 del results['.hg']
711 return results
711 return results
712
712
713 def status(self, match, subrepos, ignored, clean, unknown):
713 def status(self, match, subrepos, ignored, clean, unknown):
714 '''Determine the status of the working copy relative to the
714 '''Determine the status of the working copy relative to the
715 dirstate and return a tuple of lists (unsure, modified, added,
715 dirstate and return a tuple of lists (unsure, modified, added,
716 removed, deleted, unknown, ignored, clean), where:
716 removed, deleted, unknown, ignored, clean), where:
717
717
718 unsure:
718 unsure:
719 files that might have been modified since the dirstate was
719 files that might have been modified since the dirstate was
720 written, but need to be read to be sure (size is the same
720 written, but need to be read to be sure (size is the same
721 but mtime differs)
721 but mtime differs)
722 modified:
722 modified:
723 files that have definitely been modified since the dirstate
723 files that have definitely been modified since the dirstate
724 was written (different size or mode)
724 was written (different size or mode)
725 added:
725 added:
726 files that have been explicitly added with hg add
726 files that have been explicitly added with hg add
727 removed:
727 removed:
728 files that have been explicitly removed with hg remove
728 files that have been explicitly removed with hg remove
729 deleted:
729 deleted:
730 files that have been deleted through other means ("missing")
730 files that have been deleted through other means ("missing")
731 unknown:
731 unknown:
732 files not in the dirstate that are not ignored
732 files not in the dirstate that are not ignored
733 ignored:
733 ignored:
734 files not in the dirstate that are ignored
734 files not in the dirstate that are ignored
735 (by _dirignore())
735 (by _dirignore())
736 clean:
736 clean:
737 files that have definitely not been modified since the
737 files that have definitely not been modified since the
738 dirstate was written
738 dirstate was written
739 '''
739 '''
740 listignored, listclean, listunknown = ignored, clean, unknown
740 listignored, listclean, listunknown = ignored, clean, unknown
741 lookup, modified, added, unknown, ignored = [], [], [], [], []
741 lookup, modified, added, unknown, ignored = [], [], [], [], []
742 removed, deleted, clean = [], [], []
742 removed, deleted, clean = [], [], []
743
743
744 dmap = self._map
744 dmap = self._map
745 ladd = lookup.append # aka "unsure"
745 ladd = lookup.append # aka "unsure"
746 madd = modified.append
746 madd = modified.append
747 aadd = added.append
747 aadd = added.append
748 uadd = unknown.append
748 uadd = unknown.append
749 iadd = ignored.append
749 iadd = ignored.append
750 radd = removed.append
750 radd = removed.append
751 dadd = deleted.append
751 dadd = deleted.append
752 cadd = clean.append
752 cadd = clean.append
753 mexact = match.exact
754 dirignore = self._dirignore
755 checkexec = self._checkexec
756 checklink = self._checklink
757 copymap = self._copymap
758 lastnormaltime = self._lastnormaltime
753
759
754 lnkkind = stat.S_IFLNK
760 lnkkind = stat.S_IFLNK
755
761
756 for fn, st in self.walk(match, subrepos, listunknown,
762 for fn, st in self.walk(match, subrepos, listunknown,
757 listignored).iteritems():
763 listignored).iteritems():
758 if fn not in dmap:
764 if fn not in dmap:
759 if (listignored or match.exact(fn)) and self._dirignore(fn):
765 if (listignored or mexact(fn)) and dirignore(fn):
760 if listignored:
766 if listignored:
761 iadd(fn)
767 iadd(fn)
762 elif listunknown:
768 elif listunknown:
763 uadd(fn)
769 uadd(fn)
764 continue
770 continue
765
771
766 state, mode, size, time = dmap[fn]
772 state, mode, size, time = dmap[fn]
767
773
768 if not st and state in "nma":
774 if not st and state in "nma":
769 dadd(fn)
775 dadd(fn)
770 elif state == 'n':
776 elif state == 'n':
771 # The "mode & lnkkind != lnkkind or self._checklink"
777 # The "mode & lnkkind != lnkkind or self._checklink"
772 # lines are an expansion of "islink => checklink"
778 # lines are an expansion of "islink => checklink"
773 # where islink means "is this a link?" and checklink
779 # where islink means "is this a link?" and checklink
774 # means "can we check links?".
780 # means "can we check links?".
775 mtime = int(st.st_mtime)
781 mtime = int(st.st_mtime)
776 if (size >= 0 and
782 if (size >= 0 and
777 ((size != st.st_size and size != st.st_size & _rangemask)
783 ((size != st.st_size and size != st.st_size & _rangemask)
778 or ((mode ^ st.st_mode) & 0100 and self._checkexec))
784 or ((mode ^ st.st_mode) & 0100 and checkexec))
779 and (mode & lnkkind != lnkkind or self._checklink)
785 and (mode & lnkkind != lnkkind or checklink)
780 or size == -2 # other parent
786 or size == -2 # other parent
781 or fn in self._copymap):
787 or fn in copymap):
782 madd(fn)
788 madd(fn)
783 elif ((time != mtime and time != mtime & _rangemask)
789 elif ((time != mtime and time != mtime & _rangemask)
784 and (mode & lnkkind != lnkkind or self._checklink)):
790 and (mode & lnkkind != lnkkind or checklink)):
785 ladd(fn)
791 ladd(fn)
786 elif mtime == self._lastnormaltime:
792 elif mtime == lastnormaltime:
787 # fn may have been changed in the same timeslot without
793 # fn may have been changed in the same timeslot without
788 # changing its size. This can happen if we quickly do
794 # changing its size. This can happen if we quickly do
789 # multiple commits in a single transaction.
795 # multiple commits in a single transaction.
790 # Force lookup, so we don't miss such a racy file change.
796 # Force lookup, so we don't miss such a racy file change.
791 ladd(fn)
797 ladd(fn)
792 elif listclean:
798 elif listclean:
793 cadd(fn)
799 cadd(fn)
794 elif state == 'm':
800 elif state == 'm':
795 madd(fn)
801 madd(fn)
796 elif state == 'a':
802 elif state == 'a':
797 aadd(fn)
803 aadd(fn)
798 elif state == 'r':
804 elif state == 'r':
799 radd(fn)
805 radd(fn)
800
806
801 return (lookup, modified, added, removed, deleted, unknown, ignored,
807 return (lookup, modified, added, removed, deleted, unknown, ignored,
802 clean)
808 clean)
General Comments 0
You need to be logged in to leave comments. Login now