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