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