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