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