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