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