##// END OF EJS Templates
dirstate: hide some more internals
Matt Mackall -
r4615:9b00b73a default
parent child Browse files
Show More
@@ -1,487 +1,493 b''
1 1 """
2 2 dirstate.py - working directory tracking for mercurial
3 3
4 4 Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
5 5
6 6 This software may be used and distributed according to the terms
7 7 of the GNU General Public License, incorporated herein by reference.
8 8 """
9 9
10 10 from node import *
11 11 from i18n import _
12 12 import struct, os, time, bisect, stat, strutil, util, re, errno, ignore
13 13 import cStringIO
14 14
15 15 _unknown = ('?', 0, 0, 0)
16 16 _format = ">cllll"
17 17
18 18 class dirstate(object):
19 19
20 20 def __init__(self, opener, ui, root):
21 21 self._opener = opener
22 22 self._root = root
23 23 self._dirty = 0
24 24 self._ui = ui
25 25
26 26 def __getattr__(self, name):
27 27 if name == '_map':
28 self.read()
28 self._read()
29 29 return self._map
30 30 elif name == '_copymap':
31 self.read()
31 self._read()
32 32 return self._copymap
33 33 elif name == '_branch':
34 34 try:
35 35 self._branch = self._opener("branch").read().strip()\
36 36 or "default"
37 37 except IOError:
38 38 self._branch = "default"
39 39 return self._branch
40 40 elif name == '_pl':
41 41 self._pl = [nullid, nullid]
42 42 try:
43 43 st = self._opener("dirstate").read(40)
44 44 if len(st) == 40:
45 45 self._pl = st[:20], st[20:40]
46 46 except IOError, err:
47 47 if err.errno != errno.ENOENT: raise
48 48 return self._pl
49 elif name == 'dirs':
50 self.dirs = {}
49 elif name == '_dirs':
50 self._dirs = {}
51 51 for f in self._map:
52 self.updatedirs(f, 1)
53 return self.dirs
52 self._incpath(f)
53 return self._dirs
54 54 elif name == '_ignore':
55 55 files = [self.wjoin('.hgignore')] + self._ui.hgignorefiles()
56 56 self._ignore = ignore.ignore(self._root, files, self._ui.warn)
57 57 return self._ignore
58 58 elif name == '_slash':
59 59 self._slash = self._ui.configbool('ui', 'slash') and os.sep != '/'
60 60 return self._slash
61 61 else:
62 62 raise AttributeError, name
63 63
64 64 def wjoin(self, f):
65 65 return os.path.join(self._root, f)
66 66
67 67 def getcwd(self):
68 68 cwd = os.getcwd()
69 69 if cwd == self._root: return ''
70 70 # self._root ends with a path separator if self._root is '/' or 'C:\'
71 71 rootsep = self._root
72 72 if not rootsep.endswith(os.sep):
73 73 rootsep += os.sep
74 74 if cwd.startswith(rootsep):
75 75 return cwd[len(rootsep):]
76 76 else:
77 77 # we're outside the repo. return an absolute path.
78 78 return cwd
79 79
80 80 def pathto(self, f, cwd=None):
81 81 if cwd is None:
82 82 cwd = self.getcwd()
83 83 path = util.pathto(self._root, cwd, f)
84 84 if self._slash:
85 85 return path.replace(os.sep, '/')
86 86 return path
87 87
88 88 def __del__(self):
89 89 self.write()
90 90
91 91 def __getitem__(self, key):
92 92 return self._map[key]
93 93
94 94 def __contains__(self, key):
95 95 return key in self._map
96 96
97 97 def __iter__(self):
98 98 a = self._map.keys()
99 99 a.sort()
100 100 for x in a:
101 101 yield x
102 102
103 103 def parents(self):
104 104 return self._pl
105 105
106 106 def branch(self):
107 107 return self._branch
108 108
109 109 def markdirty(self):
110 110 self._dirty = 1
111 111
112 112 def setparents(self, p1, p2=nullid):
113 113 self.markdirty()
114 114 self._pl = p1, p2
115 115
116 116 def setbranch(self, branch):
117 117 self._branch = branch
118 118 self._opener("branch", "w").write(branch + '\n')
119 119
120 120 def state(self, key):
121 121 return self._map.get(key, ("?",))[0]
122 122
123 def read(self):
123 def _read(self):
124 124 self._map = {}
125 125 self._copymap = {}
126 126 self._pl = [nullid, nullid]
127 127 try:
128 128 st = self._opener("dirstate").read()
129 129 except IOError, err:
130 130 if err.errno != errno.ENOENT: raise
131 131 return
132 132 if not st:
133 133 return
134 134
135 135 self._pl = [st[:20], st[20: 40]]
136 136
137 137 # deref fields so they will be local in loop
138 138 dmap = self._map
139 139 copymap = self._copymap
140 140 unpack = struct.unpack
141 141
142 142 pos = 40
143 143 e_size = struct.calcsize(_format)
144 144
145 145 while pos < len(st):
146 146 newpos = pos + e_size
147 147 e = unpack(_format, st[pos:newpos])
148 148 l = e[4]
149 149 pos = newpos
150 150 newpos = pos + l
151 151 f = st[pos:newpos]
152 152 if '\0' in f:
153 153 f, c = f.split('\0')
154 154 copymap[f] = c
155 155 dmap[f] = e[:4]
156 156 pos = newpos
157 157
158 158 def invalidate(self):
159 for a in "_map _copymap _branch pl dirs _ignore".split():
159 for a in "_map _copymap _branch pl _dirs _ignore".split():
160 160 if hasattr(self, a):
161 161 self.__delattr__(a)
162 162
163 163 def copy(self, source, dest):
164 164 self.markdirty()
165 165 self._copymap[dest] = source
166 166
167 167 def copied(self, file):
168 168 return self._copymap.get(file, None)
169 169
170 170 def copies(self):
171 171 return self._copymap
172 172
173 def updatedirs(self, path, delta):
173 def _incpath(self, path):
174 174 for c in strutil.findall(path, '/'):
175 175 pc = path[:c]
176 self.dirs.setdefault(pc, 0)
177 self.dirs[pc] += delta
176 self._dirs.setdefault(pc, 0)
177 self._dirs[pc] += 1
178
179 def _decpath(self, path):
180 for c in strutil.findall(path, '/'):
181 pc = path[:c]
182 self._dirs.setdefault(pc, 0)
183 self._dirs[pc] -= 1
178 184
179 185 def checkinterfering(self, files):
180 186 def prefixes(f):
181 187 for c in strutil.rfindall(f, '/'):
182 188 yield f[:c]
183 189 seendirs = {}
184 190 for f in files:
185 191 # shadows
186 if self.dirs.get(f):
192 if self._dirs.get(f):
187 193 raise util.Abort(_('directory named %r already in dirstate') %
188 194 f)
189 195 for d in prefixes(f):
190 196 if d in seendirs:
191 197 break
192 198 if d in self._map:
193 199 raise util.Abort(_('file named %r already in dirstate') %
194 200 d)
195 201 seendirs[d] = True
196 202 # disallowed
197 203 if '\r' in f or '\n' in f:
198 204 raise util.Abort(_("'\\n' and '\\r' disallowed in filenames"))
199 205
200 206 def update(self, files, state, **kw):
201 207 ''' current states:
202 208 n normal
203 209 m needs merging
204 210 r marked for removal
205 211 a marked for addition'''
206 212
207 213 if not files: return
208 214 self.markdirty()
209 215 if state == "a":
210 216 self.checkinterfering(files)
211 217 for f in files:
212 218 if state == "r":
213 219 self._map[f] = ('r', 0, 0, 0)
214 self.updatedirs(f, -1)
220 self._decpath(f)
215 221 else:
216 222 if state == "a":
217 self.updatedirs(f, 1)
223 self._incpath(f)
218 224 s = os.lstat(self.wjoin(f))
219 225 st_size = kw.get('st_size', s.st_size)
220 226 st_mtime = kw.get('st_mtime', s.st_mtime)
221 227 self._map[f] = (state, s.st_mode, st_size, st_mtime)
222 228 if self._copymap.has_key(f):
223 229 del self._copymap[f]
224 230
225 231 def forget(self, files):
226 232 if not files: return
227 233 self.markdirty()
228 234 for f in files:
229 235 try:
230 236 del self._map[f]
231 self.updatedirs(f, -1)
237 self._decpath(f)
232 238 except KeyError:
233 239 self._ui.warn(_("not in dirstate: %s!\n") % f)
234 240 pass
235 241
236 242 def rebuild(self, parent, files):
237 243 self.invalidate()
238 244 for f in files:
239 245 if files.execf(f):
240 246 self._map[f] = ('n', 0777, -1, 0)
241 247 else:
242 248 self._map[f] = ('n', 0666, -1, 0)
243 249 self._pl = (parent, nullid)
244 250 self.markdirty()
245 251
246 252 def write(self):
247 253 if not self._dirty:
248 254 return
249 255 cs = cStringIO.StringIO()
250 256 cs.write("".join(self._pl))
251 257 for f, e in self._map.iteritems():
252 258 c = self.copied(f)
253 259 if c:
254 260 f = f + "\0" + c
255 261 e = struct.pack(_format, e[0], e[1], e[2], e[3], len(f))
256 262 cs.write(e)
257 263 cs.write(f)
258 264 st = self._opener("dirstate", "w", atomictemp=True)
259 265 st.write(cs.getvalue())
260 266 st.rename()
261 267 self._dirty = 0
262 268
263 269 def filterfiles(self, files):
264 270 ret = {}
265 271 unknown = []
266 272
267 273 for x in files:
268 274 if x == '.':
269 275 return self._map.copy()
270 276 if x not in self._map:
271 277 unknown.append(x)
272 278 else:
273 279 ret[x] = self._map[x]
274 280
275 281 if not unknown:
276 282 return ret
277 283
278 284 b = self._map.keys()
279 285 b.sort()
280 286 blen = len(b)
281 287
282 288 for x in unknown:
283 289 bs = bisect.bisect(b, "%s%s" % (x, '/'))
284 290 while bs < blen:
285 291 s = b[bs]
286 292 if len(s) > len(x) and s.startswith(x):
287 293 ret[s] = self._map[s]
288 294 else:
289 295 break
290 296 bs += 1
291 297 return ret
292 298
293 def supported_type(self, f, st, verbose=False):
299 def _supported(self, f, st, verbose=False):
294 300 if stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode):
295 301 return True
296 302 if verbose:
297 303 kind = 'unknown'
298 304 if stat.S_ISCHR(st.st_mode): kind = _('character device')
299 305 elif stat.S_ISBLK(st.st_mode): kind = _('block device')
300 306 elif stat.S_ISFIFO(st.st_mode): kind = _('fifo')
301 307 elif stat.S_ISSOCK(st.st_mode): kind = _('socket')
302 308 elif stat.S_ISDIR(st.st_mode): kind = _('directory')
303 309 self._ui.warn(_('%s: unsupported file type (type is %s)\n')
304 310 % (self.pathto(f), kind))
305 311 return False
306 312
307 313 def walk(self, files=None, match=util.always, badmatch=None):
308 314 # filter out the stat
309 315 for src, f, st in self.statwalk(files, match, badmatch=badmatch):
310 316 yield src, f
311 317
312 318 def statwalk(self, files=None, match=util.always, ignored=False,
313 319 badmatch=None, directories=False):
314 320 '''
315 321 walk recursively through the directory tree, finding all files
316 322 matched by the match function
317 323
318 324 results are yielded in a tuple (src, filename, st), where src
319 325 is one of:
320 326 'f' the file was found in the directory tree
321 327 'd' the file is a directory of the tree
322 328 'm' the file was only in the dirstate and not in the tree
323 329 'b' file was not found and matched badmatch
324 330
325 331 and st is the stat result if the file was found in the directory.
326 332 '''
327 333
328 334 # walk all files by default
329 335 if not files:
330 336 files = ['.']
331 337 dc = self._map.copy()
332 338 else:
333 339 files = util.unique(files)
334 340 dc = self.filterfiles(files)
335 341
336 342 def imatch(file_):
337 343 if file_ not in dc and self._ignore(file_):
338 344 return False
339 345 return match(file_)
340 346
341 347 ignore = self._ignore
342 348 if ignored:
343 349 imatch = match
344 350 ignore = util.never
345 351
346 352 # self._root may end with a path separator when self._root == '/'
347 353 common_prefix_len = len(self._root)
348 354 if not self._root.endswith(os.sep):
349 355 common_prefix_len += 1
350 356 # recursion free walker, faster than os.walk.
351 357 def findfiles(s):
352 358 work = [s]
353 359 if directories:
354 360 yield 'd', util.normpath(s[common_prefix_len:]), os.lstat(s)
355 361 while work:
356 362 top = work.pop()
357 363 names = os.listdir(top)
358 364 names.sort()
359 365 # nd is the top of the repository dir tree
360 366 nd = util.normpath(top[common_prefix_len:])
361 367 if nd == '.':
362 368 nd = ''
363 369 else:
364 370 # do not recurse into a repo contained in this
365 371 # one. use bisect to find .hg directory so speed
366 372 # is good on big directory.
367 373 hg = bisect.bisect_left(names, '.hg')
368 374 if hg < len(names) and names[hg] == '.hg':
369 375 if os.path.isdir(os.path.join(top, '.hg')):
370 376 continue
371 377 for f in names:
372 378 np = util.pconvert(os.path.join(nd, f))
373 379 if seen(np):
374 380 continue
375 381 p = os.path.join(top, f)
376 382 # don't trip over symlinks
377 383 st = os.lstat(p)
378 384 if stat.S_ISDIR(st.st_mode):
379 385 if not ignore(np):
380 386 work.append(p)
381 387 if directories:
382 388 yield 'd', np, st
383 389 if imatch(np) and np in dc:
384 390 yield 'm', np, st
385 391 elif imatch(np):
386 if self.supported_type(np, st):
392 if self._supported(np, st):
387 393 yield 'f', np, st
388 394 elif np in dc:
389 395 yield 'm', np, st
390 396
391 397 known = {'.hg': 1}
392 398 def seen(fn):
393 399 if fn in known: return True
394 400 known[fn] = 1
395 401
396 402 # step one, find all files that match our criteria
397 403 files.sort()
398 404 for ff in files:
399 405 nf = util.normpath(ff)
400 406 f = self.wjoin(ff)
401 407 try:
402 408 st = os.lstat(f)
403 409 except OSError, inst:
404 410 found = False
405 411 for fn in dc:
406 412 if nf == fn or (fn.startswith(nf) and fn[len(nf)] == '/'):
407 413 found = True
408 414 break
409 415 if not found:
410 416 if inst.errno != errno.ENOENT or not badmatch:
411 417 self._ui.warn('%s: %s\n' % (self.pathto(ff),
412 418 inst.strerror))
413 419 elif badmatch and badmatch(ff) and imatch(nf):
414 420 yield 'b', ff, None
415 421 continue
416 422 if stat.S_ISDIR(st.st_mode):
417 423 cmp1 = (lambda x, y: cmp(x[1], y[1]))
418 424 sorted_ = [ x for x in findfiles(f) ]
419 425 sorted_.sort(cmp1)
420 426 for e in sorted_:
421 427 yield e
422 428 else:
423 429 if not seen(nf) and match(nf):
424 if self.supported_type(ff, st, verbose=True):
430 if self._supported(ff, st, verbose=True):
425 431 yield 'f', nf, st
426 432 elif ff in dc:
427 433 yield 'm', nf, st
428 434
429 435 # step two run through anything left in the dc hash and yield
430 436 # if we haven't already seen it
431 437 ks = dc.keys()
432 438 ks.sort()
433 439 for k in ks:
434 440 if not seen(k) and imatch(k):
435 441 yield 'm', k, None
436 442
437 443 def status(self, files=None, match=util.always, list_ignored=False,
438 444 list_clean=False):
439 445 lookup, modified, added, unknown, ignored = [], [], [], [], []
440 446 removed, deleted, clean = [], [], []
441 447
442 448 for src, fn, st in self.statwalk(files, match, ignored=list_ignored):
443 449 try:
444 450 type_, mode, size, time = self[fn]
445 451 except KeyError:
446 452 if list_ignored and self._ignore(fn):
447 453 ignored.append(fn)
448 454 else:
449 455 unknown.append(fn)
450 456 continue
451 457 if src == 'm':
452 458 nonexistent = True
453 459 if not st:
454 460 try:
455 461 st = os.lstat(self.wjoin(fn))
456 462 except OSError, inst:
457 463 if inst.errno != errno.ENOENT:
458 464 raise
459 465 st = None
460 466 # We need to re-check that it is a valid file
461 if st and self.supported_type(fn, st):
467 if st and self._supported(fn, st):
462 468 nonexistent = False
463 469 # XXX: what to do with file no longer present in the fs
464 470 # who are not removed in the dirstate ?
465 471 if nonexistent and type_ in "nm":
466 472 deleted.append(fn)
467 473 continue
468 474 # check the common case first
469 475 if type_ == 'n':
470 476 if not st:
471 477 st = os.lstat(self.wjoin(fn))
472 478 if size >= 0 and (size != st.st_size
473 479 or (mode ^ st.st_mode) & 0100):
474 480 modified.append(fn)
475 481 elif time != int(st.st_mtime):
476 482 lookup.append(fn)
477 483 elif list_clean:
478 484 clean.append(fn)
479 485 elif type_ == 'm':
480 486 modified.append(fn)
481 487 elif type_ == 'a':
482 488 added.append(fn)
483 489 elif type_ == 'r':
484 490 removed.append(fn)
485 491
486 492 return (lookup, modified, added, removed, deleted, unknown, ignored,
487 493 clean)
General Comments 0
You need to be logged in to leave comments. Login now