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