##// END OF EJS Templates
dirstate: make functions for backup aware of transaction activity...
FUJIWARA Katsunori -
r26633:020b12d5 default
parent child Browse files
Show More
@@ -1,1072 +1,1097 b''
1 1 # dirstate.py - working directory tracking for mercurial
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from node import nullid
9 9 from i18n import _
10 10 import scmutil, util, osutil, parsers, encoding, pathutil, error
11 11 import os, stat, errno
12 12 import match as matchmod
13 13
14 14 propertycache = util.propertycache
15 15 filecache = scmutil.filecache
16 16 _rangemask = 0x7fffffff
17 17
18 18 dirstatetuple = parsers.dirstatetuple
19 19
20 20 class repocache(filecache):
21 21 """filecache for files in .hg/"""
22 22 def join(self, obj, fname):
23 23 return obj._opener.join(fname)
24 24
25 25 class rootcache(filecache):
26 26 """filecache for files in the repository root"""
27 27 def join(self, obj, fname):
28 28 return obj._join(fname)
29 29
30 30 class dirstate(object):
31 31
32 32 def __init__(self, opener, ui, root, validate):
33 33 '''Create a new dirstate object.
34 34
35 35 opener is an open()-like callable that can be used to open the
36 36 dirstate file; root is the root of the directory tracked by
37 37 the dirstate.
38 38 '''
39 39 self._opener = opener
40 40 self._validate = validate
41 41 self._root = root
42 42 # ntpath.join(root, '') of Python 2.7.9 does not add sep if root is
43 43 # UNC path pointing to root share (issue4557)
44 44 self._rootdir = pathutil.normasprefix(root)
45 45 # internal config: ui.forcecwd
46 46 forcecwd = ui.config('ui', 'forcecwd')
47 47 if forcecwd:
48 48 self._cwd = forcecwd
49 49 self._dirty = False
50 50 self._dirtypl = False
51 51 self._lastnormaltime = 0
52 52 self._ui = ui
53 53 self._filecache = {}
54 54 self._parentwriters = 0
55 55 self._filename = 'dirstate'
56 self._pendingfilename = '%s.pending' % self._filename
56 57
57 58 def beginparentchange(self):
58 59 '''Marks the beginning of a set of changes that involve changing
59 60 the dirstate parents. If there is an exception during this time,
60 61 the dirstate will not be written when the wlock is released. This
61 62 prevents writing an incoherent dirstate where the parent doesn't
62 63 match the contents.
63 64 '''
64 65 self._parentwriters += 1
65 66
66 67 def endparentchange(self):
67 68 '''Marks the end of a set of changes that involve changing the
68 69 dirstate parents. Once all parent changes have been marked done,
69 70 the wlock will be free to write the dirstate on release.
70 71 '''
71 72 if self._parentwriters > 0:
72 73 self._parentwriters -= 1
73 74
74 75 def pendingparentchange(self):
75 76 '''Returns true if the dirstate is in the middle of a set of changes
76 77 that modify the dirstate parent.
77 78 '''
78 79 return self._parentwriters > 0
79 80
80 81 @propertycache
81 82 def _map(self):
82 83 '''Return the dirstate contents as a map from filename to
83 84 (state, mode, size, time).'''
84 85 self._read()
85 86 return self._map
86 87
87 88 @propertycache
88 89 def _copymap(self):
89 90 self._read()
90 91 return self._copymap
91 92
92 93 @propertycache
93 94 def _filefoldmap(self):
94 95 try:
95 96 makefilefoldmap = parsers.make_file_foldmap
96 97 except AttributeError:
97 98 pass
98 99 else:
99 100 return makefilefoldmap(self._map, util.normcasespec,
100 101 util.normcasefallback)
101 102
102 103 f = {}
103 104 normcase = util.normcase
104 105 for name, s in self._map.iteritems():
105 106 if s[0] != 'r':
106 107 f[normcase(name)] = name
107 108 f['.'] = '.' # prevents useless util.fspath() invocation
108 109 return f
109 110
110 111 @propertycache
111 112 def _dirfoldmap(self):
112 113 f = {}
113 114 normcase = util.normcase
114 115 for name in self._dirs:
115 116 f[normcase(name)] = name
116 117 return f
117 118
118 119 @repocache('branch')
119 120 def _branch(self):
120 121 try:
121 122 return self._opener.read("branch").strip() or "default"
122 123 except IOError as inst:
123 124 if inst.errno != errno.ENOENT:
124 125 raise
125 126 return "default"
126 127
127 128 @propertycache
128 129 def _pl(self):
129 130 try:
130 131 fp = self._opener(self._filename)
131 132 st = fp.read(40)
132 133 fp.close()
133 134 l = len(st)
134 135 if l == 40:
135 136 return st[:20], st[20:40]
136 137 elif l > 0 and l < 40:
137 138 raise error.Abort(_('working directory state appears damaged!'))
138 139 except IOError as err:
139 140 if err.errno != errno.ENOENT:
140 141 raise
141 142 return [nullid, nullid]
142 143
143 144 @propertycache
144 145 def _dirs(self):
145 146 return util.dirs(self._map, 'r')
146 147
147 148 def dirs(self):
148 149 return self._dirs
149 150
150 151 @rootcache('.hgignore')
151 152 def _ignore(self):
152 153 files = []
153 154 if os.path.exists(self._join('.hgignore')):
154 155 files.append(self._join('.hgignore'))
155 156 for name, path in self._ui.configitems("ui"):
156 157 if name == 'ignore' or name.startswith('ignore.'):
157 158 # we need to use os.path.join here rather than self._join
158 159 # because path is arbitrary and user-specified
159 160 files.append(os.path.join(self._rootdir, util.expandpath(path)))
160 161
161 162 if not files:
162 163 return util.never
163 164
164 165 pats = ['include:%s' % f for f in files]
165 166 return matchmod.match(self._root, '', [], pats, warn=self._ui.warn)
166 167
167 168 @propertycache
168 169 def _slash(self):
169 170 return self._ui.configbool('ui', 'slash') and os.sep != '/'
170 171
171 172 @propertycache
172 173 def _checklink(self):
173 174 return util.checklink(self._root)
174 175
175 176 @propertycache
176 177 def _checkexec(self):
177 178 return util.checkexec(self._root)
178 179
179 180 @propertycache
180 181 def _checkcase(self):
181 182 return not util.checkcase(self._join('.hg'))
182 183
183 184 def _join(self, f):
184 185 # much faster than os.path.join()
185 186 # it's safe because f is always a relative path
186 187 return self._rootdir + f
187 188
188 189 def flagfunc(self, buildfallback):
189 190 if self._checklink and self._checkexec:
190 191 def f(x):
191 192 try:
192 193 st = os.lstat(self._join(x))
193 194 if util.statislink(st):
194 195 return 'l'
195 196 if util.statisexec(st):
196 197 return 'x'
197 198 except OSError:
198 199 pass
199 200 return ''
200 201 return f
201 202
202 203 fallback = buildfallback()
203 204 if self._checklink:
204 205 def f(x):
205 206 if os.path.islink(self._join(x)):
206 207 return 'l'
207 208 if 'x' in fallback(x):
208 209 return 'x'
209 210 return ''
210 211 return f
211 212 if self._checkexec:
212 213 def f(x):
213 214 if 'l' in fallback(x):
214 215 return 'l'
215 216 if util.isexec(self._join(x)):
216 217 return 'x'
217 218 return ''
218 219 return f
219 220 else:
220 221 return fallback
221 222
222 223 @propertycache
223 224 def _cwd(self):
224 225 return os.getcwd()
225 226
226 227 def getcwd(self):
227 228 '''Return the path from which a canonical path is calculated.
228 229
229 230 This path should be used to resolve file patterns or to convert
230 231 canonical paths back to file paths for display. It shouldn't be
231 232 used to get real file paths. Use vfs functions instead.
232 233 '''
233 234 cwd = self._cwd
234 235 if cwd == self._root:
235 236 return ''
236 237 # self._root ends with a path separator if self._root is '/' or 'C:\'
237 238 rootsep = self._root
238 239 if not util.endswithsep(rootsep):
239 240 rootsep += os.sep
240 241 if cwd.startswith(rootsep):
241 242 return cwd[len(rootsep):]
242 243 else:
243 244 # we're outside the repo. return an absolute path.
244 245 return cwd
245 246
246 247 def pathto(self, f, cwd=None):
247 248 if cwd is None:
248 249 cwd = self.getcwd()
249 250 path = util.pathto(self._root, cwd, f)
250 251 if self._slash:
251 252 return util.pconvert(path)
252 253 return path
253 254
254 255 def __getitem__(self, key):
255 256 '''Return the current state of key (a filename) in the dirstate.
256 257
257 258 States are:
258 259 n normal
259 260 m needs merging
260 261 r marked for removal
261 262 a marked for addition
262 263 ? not tracked
263 264 '''
264 265 return self._map.get(key, ("?",))[0]
265 266
266 267 def __contains__(self, key):
267 268 return key in self._map
268 269
269 270 def __iter__(self):
270 271 for x in sorted(self._map):
271 272 yield x
272 273
273 274 def iteritems(self):
274 275 return self._map.iteritems()
275 276
276 277 def parents(self):
277 278 return [self._validate(p) for p in self._pl]
278 279
279 280 def p1(self):
280 281 return self._validate(self._pl[0])
281 282
282 283 def p2(self):
283 284 return self._validate(self._pl[1])
284 285
285 286 def branch(self):
286 287 return encoding.tolocal(self._branch)
287 288
288 289 def setparents(self, p1, p2=nullid):
289 290 """Set dirstate parents to p1 and p2.
290 291
291 292 When moving from two parents to one, 'm' merged entries a
292 293 adjusted to normal and previous copy records discarded and
293 294 returned by the call.
294 295
295 296 See localrepo.setparents()
296 297 """
297 298 if self._parentwriters == 0:
298 299 raise ValueError("cannot set dirstate parent without "
299 300 "calling dirstate.beginparentchange")
300 301
301 302 self._dirty = self._dirtypl = True
302 303 oldp2 = self._pl[1]
303 304 self._pl = p1, p2
304 305 copies = {}
305 306 if oldp2 != nullid and p2 == nullid:
306 307 for f, s in self._map.iteritems():
307 308 # Discard 'm' markers when moving away from a merge state
308 309 if s[0] == 'm':
309 310 if f in self._copymap:
310 311 copies[f] = self._copymap[f]
311 312 self.normallookup(f)
312 313 # Also fix up otherparent markers
313 314 elif s[0] == 'n' and s[2] == -2:
314 315 if f in self._copymap:
315 316 copies[f] = self._copymap[f]
316 317 self.add(f)
317 318 return copies
318 319
319 320 def setbranch(self, branch):
320 321 self._branch = encoding.fromlocal(branch)
321 322 f = self._opener('branch', 'w', atomictemp=True)
322 323 try:
323 324 f.write(self._branch + '\n')
324 325 f.close()
325 326
326 327 # make sure filecache has the correct stat info for _branch after
327 328 # replacing the underlying file
328 329 ce = self._filecache['_branch']
329 330 if ce:
330 331 ce.refresh()
331 332 except: # re-raises
332 333 f.discard()
333 334 raise
334 335
335 336 def _read(self):
336 337 self._map = {}
337 338 self._copymap = {}
338 339 try:
339 340 fp = self._opener.open(self._filename)
340 341 try:
341 342 st = fp.read()
342 343 finally:
343 344 fp.close()
344 345 except IOError as err:
345 346 if err.errno != errno.ENOENT:
346 347 raise
347 348 return
348 349 if not st:
349 350 return
350 351
351 352 if util.safehasattr(parsers, 'dict_new_presized'):
352 353 # Make an estimate of the number of files in the dirstate based on
353 354 # its size. From a linear regression on a set of real-world repos,
354 355 # all over 10,000 files, the size of a dirstate entry is 85
355 356 # bytes. The cost of resizing is significantly higher than the cost
356 357 # of filling in a larger presized dict, so subtract 20% from the
357 358 # size.
358 359 #
359 360 # This heuristic is imperfect in many ways, so in a future dirstate
360 361 # format update it makes sense to just record the number of entries
361 362 # on write.
362 363 self._map = parsers.dict_new_presized(len(st) / 71)
363 364
364 365 # Python's garbage collector triggers a GC each time a certain number
365 366 # of container objects (the number being defined by
366 367 # gc.get_threshold()) are allocated. parse_dirstate creates a tuple
367 368 # for each file in the dirstate. The C version then immediately marks
368 369 # them as not to be tracked by the collector. However, this has no
369 370 # effect on when GCs are triggered, only on what objects the GC looks
370 371 # into. This means that O(number of files) GCs are unavoidable.
371 372 # Depending on when in the process's lifetime the dirstate is parsed,
372 373 # this can get very expensive. As a workaround, disable GC while
373 374 # parsing the dirstate.
374 375 #
375 376 # (we cannot decorate the function directly since it is in a C module)
376 377 parse_dirstate = util.nogc(parsers.parse_dirstate)
377 378 p = parse_dirstate(self._map, self._copymap, st)
378 379 if not self._dirtypl:
379 380 self._pl = p
380 381
381 382 def invalidate(self):
382 383 for a in ("_map", "_copymap", "_filefoldmap", "_dirfoldmap", "_branch",
383 384 "_pl", "_dirs", "_ignore"):
384 385 if a in self.__dict__:
385 386 delattr(self, a)
386 387 self._lastnormaltime = 0
387 388 self._dirty = False
388 389 self._parentwriters = 0
389 390
390 391 def copy(self, source, dest):
391 392 """Mark dest as a copy of source. Unmark dest if source is None."""
392 393 if source == dest:
393 394 return
394 395 self._dirty = True
395 396 if source is not None:
396 397 self._copymap[dest] = source
397 398 elif dest in self._copymap:
398 399 del self._copymap[dest]
399 400
400 401 def copied(self, file):
401 402 return self._copymap.get(file, None)
402 403
403 404 def copies(self):
404 405 return self._copymap
405 406
406 407 def _droppath(self, f):
407 408 if self[f] not in "?r" and "_dirs" in self.__dict__:
408 409 self._dirs.delpath(f)
409 410
410 411 def _addpath(self, f, state, mode, size, mtime):
411 412 oldstate = self[f]
412 413 if state == 'a' or oldstate == 'r':
413 414 scmutil.checkfilename(f)
414 415 if f in self._dirs:
415 416 raise error.Abort(_('directory %r already in dirstate') % f)
416 417 # shadows
417 418 for d in util.finddirs(f):
418 419 if d in self._dirs:
419 420 break
420 421 if d in self._map and self[d] != 'r':
421 422 raise error.Abort(
422 423 _('file %r in dirstate clashes with %r') % (d, f))
423 424 if oldstate in "?r" and "_dirs" in self.__dict__:
424 425 self._dirs.addpath(f)
425 426 self._dirty = True
426 427 self._map[f] = dirstatetuple(state, mode, size, mtime)
427 428
428 429 def normal(self, f):
429 430 '''Mark a file normal and clean.'''
430 431 s = os.lstat(self._join(f))
431 432 mtime = util.statmtimesec(s)
432 433 self._addpath(f, 'n', s.st_mode,
433 434 s.st_size & _rangemask, mtime & _rangemask)
434 435 if f in self._copymap:
435 436 del self._copymap[f]
436 437 if mtime > self._lastnormaltime:
437 438 # Remember the most recent modification timeslot for status(),
438 439 # to make sure we won't miss future size-preserving file content
439 440 # modifications that happen within the same timeslot.
440 441 self._lastnormaltime = mtime
441 442
442 443 def normallookup(self, f):
443 444 '''Mark a file normal, but possibly dirty.'''
444 445 if self._pl[1] != nullid and f in self._map:
445 446 # if there is a merge going on and the file was either
446 447 # in state 'm' (-1) or coming from other parent (-2) before
447 448 # being removed, restore that state.
448 449 entry = self._map[f]
449 450 if entry[0] == 'r' and entry[2] in (-1, -2):
450 451 source = self._copymap.get(f)
451 452 if entry[2] == -1:
452 453 self.merge(f)
453 454 elif entry[2] == -2:
454 455 self.otherparent(f)
455 456 if source:
456 457 self.copy(source, f)
457 458 return
458 459 if entry[0] == 'm' or entry[0] == 'n' and entry[2] == -2:
459 460 return
460 461 self._addpath(f, 'n', 0, -1, -1)
461 462 if f in self._copymap:
462 463 del self._copymap[f]
463 464
464 465 def otherparent(self, f):
465 466 '''Mark as coming from the other parent, always dirty.'''
466 467 if self._pl[1] == nullid:
467 468 raise error.Abort(_("setting %r to other parent "
468 469 "only allowed in merges") % f)
469 470 if f in self and self[f] == 'n':
470 471 # merge-like
471 472 self._addpath(f, 'm', 0, -2, -1)
472 473 else:
473 474 # add-like
474 475 self._addpath(f, 'n', 0, -2, -1)
475 476
476 477 if f in self._copymap:
477 478 del self._copymap[f]
478 479
479 480 def add(self, f):
480 481 '''Mark a file added.'''
481 482 self._addpath(f, 'a', 0, -1, -1)
482 483 if f in self._copymap:
483 484 del self._copymap[f]
484 485
485 486 def remove(self, f):
486 487 '''Mark a file removed.'''
487 488 self._dirty = True
488 489 self._droppath(f)
489 490 size = 0
490 491 if self._pl[1] != nullid and f in self._map:
491 492 # backup the previous state
492 493 entry = self._map[f]
493 494 if entry[0] == 'm': # merge
494 495 size = -1
495 496 elif entry[0] == 'n' and entry[2] == -2: # other parent
496 497 size = -2
497 498 self._map[f] = dirstatetuple('r', 0, size, 0)
498 499 if size == 0 and f in self._copymap:
499 500 del self._copymap[f]
500 501
501 502 def merge(self, f):
502 503 '''Mark a file merged.'''
503 504 if self._pl[1] == nullid:
504 505 return self.normallookup(f)
505 506 return self.otherparent(f)
506 507
507 508 def drop(self, f):
508 509 '''Drop a file from the dirstate'''
509 510 if f in self._map:
510 511 self._dirty = True
511 512 self._droppath(f)
512 513 del self._map[f]
513 514
514 515 def _discoverpath(self, path, normed, ignoremissing, exists, storemap):
515 516 if exists is None:
516 517 exists = os.path.lexists(os.path.join(self._root, path))
517 518 if not exists:
518 519 # Maybe a path component exists
519 520 if not ignoremissing and '/' in path:
520 521 d, f = path.rsplit('/', 1)
521 522 d = self._normalize(d, False, ignoremissing, None)
522 523 folded = d + "/" + f
523 524 else:
524 525 # No path components, preserve original case
525 526 folded = path
526 527 else:
527 528 # recursively normalize leading directory components
528 529 # against dirstate
529 530 if '/' in normed:
530 531 d, f = normed.rsplit('/', 1)
531 532 d = self._normalize(d, False, ignoremissing, True)
532 533 r = self._root + "/" + d
533 534 folded = d + "/" + util.fspath(f, r)
534 535 else:
535 536 folded = util.fspath(normed, self._root)
536 537 storemap[normed] = folded
537 538
538 539 return folded
539 540
540 541 def _normalizefile(self, path, isknown, ignoremissing=False, exists=None):
541 542 normed = util.normcase(path)
542 543 folded = self._filefoldmap.get(normed, None)
543 544 if folded is None:
544 545 if isknown:
545 546 folded = path
546 547 else:
547 548 folded = self._discoverpath(path, normed, ignoremissing, exists,
548 549 self._filefoldmap)
549 550 return folded
550 551
551 552 def _normalize(self, path, isknown, ignoremissing=False, exists=None):
552 553 normed = util.normcase(path)
553 554 folded = self._filefoldmap.get(normed, None)
554 555 if folded is None:
555 556 folded = self._dirfoldmap.get(normed, None)
556 557 if folded is None:
557 558 if isknown:
558 559 folded = path
559 560 else:
560 561 # store discovered result in dirfoldmap so that future
561 562 # normalizefile calls don't start matching directories
562 563 folded = self._discoverpath(path, normed, ignoremissing, exists,
563 564 self._dirfoldmap)
564 565 return folded
565 566
566 567 def normalize(self, path, isknown=False, ignoremissing=False):
567 568 '''
568 569 normalize the case of a pathname when on a casefolding filesystem
569 570
570 571 isknown specifies whether the filename came from walking the
571 572 disk, to avoid extra filesystem access.
572 573
573 574 If ignoremissing is True, missing path are returned
574 575 unchanged. Otherwise, we try harder to normalize possibly
575 576 existing path components.
576 577
577 578 The normalized case is determined based on the following precedence:
578 579
579 580 - version of name already stored in the dirstate
580 581 - version of name stored on disk
581 582 - version provided via command arguments
582 583 '''
583 584
584 585 if self._checkcase:
585 586 return self._normalize(path, isknown, ignoremissing)
586 587 return path
587 588
588 589 def clear(self):
589 590 self._map = {}
590 591 if "_dirs" in self.__dict__:
591 592 delattr(self, "_dirs")
592 593 self._copymap = {}
593 594 self._pl = [nullid, nullid]
594 595 self._lastnormaltime = 0
595 596 self._dirty = True
596 597
597 598 def rebuild(self, parent, allfiles, changedfiles=None):
598 599 if changedfiles is None:
599 600 changedfiles = allfiles
600 601 oldmap = self._map
601 602 self.clear()
602 603 for f in allfiles:
603 604 if f not in changedfiles:
604 605 self._map[f] = oldmap[f]
605 606 else:
606 607 if 'x' in allfiles.flags(f):
607 608 self._map[f] = dirstatetuple('n', 0o777, -1, 0)
608 609 else:
609 610 self._map[f] = dirstatetuple('n', 0o666, -1, 0)
610 611 self._pl = (parent, nullid)
611 612 self._dirty = True
612 613
613 614 def write(self):
614 615 if not self._dirty:
615 616 return
616 617
617 618 # enough 'delaywrite' prevents 'pack_dirstate' from dropping
618 619 # timestamp of each entries in dirstate, because of 'now > mtime'
619 620 delaywrite = self._ui.configint('debug', 'dirstate.delaywrite', 0)
620 621 if delaywrite > 0:
621 622 import time # to avoid useless import
622 623 time.sleep(delaywrite)
623 624
624 625 st = self._opener(self._filename, "w", atomictemp=True)
625 626 self._writedirstate(st)
626 627
627 628 def _writedirstate(self, st):
628 629 # use the modification time of the newly created temporary file as the
629 630 # filesystem's notion of 'now'
630 631 now = util.statmtimesec(util.fstat(st)) & _rangemask
631 632 st.write(parsers.pack_dirstate(self._map, self._copymap, self._pl, now))
632 633 st.close()
633 634 self._lastnormaltime = 0
634 635 self._dirty = self._dirtypl = False
635 636
636 637 def _dirignore(self, f):
637 638 if f == '.':
638 639 return False
639 640 if self._ignore(f):
640 641 return True
641 642 for p in util.finddirs(f):
642 643 if self._ignore(p):
643 644 return True
644 645 return False
645 646
646 647 def _walkexplicit(self, match, subrepos):
647 648 '''Get stat data about the files explicitly specified by match.
648 649
649 650 Return a triple (results, dirsfound, dirsnotfound).
650 651 - results is a mapping from filename to stat result. It also contains
651 652 listings mapping subrepos and .hg to None.
652 653 - dirsfound is a list of files found to be directories.
653 654 - dirsnotfound is a list of files that the dirstate thinks are
654 655 directories and that were not found.'''
655 656
656 657 def badtype(mode):
657 658 kind = _('unknown')
658 659 if stat.S_ISCHR(mode):
659 660 kind = _('character device')
660 661 elif stat.S_ISBLK(mode):
661 662 kind = _('block device')
662 663 elif stat.S_ISFIFO(mode):
663 664 kind = _('fifo')
664 665 elif stat.S_ISSOCK(mode):
665 666 kind = _('socket')
666 667 elif stat.S_ISDIR(mode):
667 668 kind = _('directory')
668 669 return _('unsupported file type (type is %s)') % kind
669 670
670 671 matchedir = match.explicitdir
671 672 badfn = match.bad
672 673 dmap = self._map
673 674 lstat = os.lstat
674 675 getkind = stat.S_IFMT
675 676 dirkind = stat.S_IFDIR
676 677 regkind = stat.S_IFREG
677 678 lnkkind = stat.S_IFLNK
678 679 join = self._join
679 680 dirsfound = []
680 681 foundadd = dirsfound.append
681 682 dirsnotfound = []
682 683 notfoundadd = dirsnotfound.append
683 684
684 685 if not match.isexact() and self._checkcase:
685 686 normalize = self._normalize
686 687 else:
687 688 normalize = None
688 689
689 690 files = sorted(match.files())
690 691 subrepos.sort()
691 692 i, j = 0, 0
692 693 while i < len(files) and j < len(subrepos):
693 694 subpath = subrepos[j] + "/"
694 695 if files[i] < subpath:
695 696 i += 1
696 697 continue
697 698 while i < len(files) and files[i].startswith(subpath):
698 699 del files[i]
699 700 j += 1
700 701
701 702 if not files or '.' in files:
702 703 files = ['.']
703 704 results = dict.fromkeys(subrepos)
704 705 results['.hg'] = None
705 706
706 707 alldirs = None
707 708 for ff in files:
708 709 # constructing the foldmap is expensive, so don't do it for the
709 710 # common case where files is ['.']
710 711 if normalize and ff != '.':
711 712 nf = normalize(ff, False, True)
712 713 else:
713 714 nf = ff
714 715 if nf in results:
715 716 continue
716 717
717 718 try:
718 719 st = lstat(join(nf))
719 720 kind = getkind(st.st_mode)
720 721 if kind == dirkind:
721 722 if nf in dmap:
722 723 # file replaced by dir on disk but still in dirstate
723 724 results[nf] = None
724 725 if matchedir:
725 726 matchedir(nf)
726 727 foundadd((nf, ff))
727 728 elif kind == regkind or kind == lnkkind:
728 729 results[nf] = st
729 730 else:
730 731 badfn(ff, badtype(kind))
731 732 if nf in dmap:
732 733 results[nf] = None
733 734 except OSError as inst: # nf not found on disk - it is dirstate only
734 735 if nf in dmap: # does it exactly match a missing file?
735 736 results[nf] = None
736 737 else: # does it match a missing directory?
737 738 if alldirs is None:
738 739 alldirs = util.dirs(dmap)
739 740 if nf in alldirs:
740 741 if matchedir:
741 742 matchedir(nf)
742 743 notfoundadd(nf)
743 744 else:
744 745 badfn(ff, inst.strerror)
745 746
746 747 # Case insensitive filesystems cannot rely on lstat() failing to detect
747 748 # a case-only rename. Prune the stat object for any file that does not
748 749 # match the case in the filesystem, if there are multiple files that
749 750 # normalize to the same path.
750 751 if match.isexact() and self._checkcase:
751 752 normed = {}
752 753
753 754 for f, st in results.iteritems():
754 755 if st is None:
755 756 continue
756 757
757 758 nc = util.normcase(f)
758 759 paths = normed.get(nc)
759 760
760 761 if paths is None:
761 762 paths = set()
762 763 normed[nc] = paths
763 764
764 765 paths.add(f)
765 766
766 767 for norm, paths in normed.iteritems():
767 768 if len(paths) > 1:
768 769 for path in paths:
769 770 folded = self._discoverpath(path, norm, True, None,
770 771 self._dirfoldmap)
771 772 if path != folded:
772 773 results[path] = None
773 774
774 775 return results, dirsfound, dirsnotfound
775 776
776 777 def walk(self, match, subrepos, unknown, ignored, full=True):
777 778 '''
778 779 Walk recursively through the directory tree, finding all files
779 780 matched by match.
780 781
781 782 If full is False, maybe skip some known-clean files.
782 783
783 784 Return a dict mapping filename to stat-like object (either
784 785 mercurial.osutil.stat instance or return value of os.stat()).
785 786
786 787 '''
787 788 # full is a flag that extensions that hook into walk can use -- this
788 789 # implementation doesn't use it at all. This satisfies the contract
789 790 # because we only guarantee a "maybe".
790 791
791 792 if ignored:
792 793 ignore = util.never
793 794 dirignore = util.never
794 795 elif unknown:
795 796 ignore = self._ignore
796 797 dirignore = self._dirignore
797 798 else:
798 799 # if not unknown and not ignored, drop dir recursion and step 2
799 800 ignore = util.always
800 801 dirignore = util.always
801 802
802 803 matchfn = match.matchfn
803 804 matchalways = match.always()
804 805 matchtdir = match.traversedir
805 806 dmap = self._map
806 807 listdir = osutil.listdir
807 808 lstat = os.lstat
808 809 dirkind = stat.S_IFDIR
809 810 regkind = stat.S_IFREG
810 811 lnkkind = stat.S_IFLNK
811 812 join = self._join
812 813
813 814 exact = skipstep3 = False
814 815 if match.isexact(): # match.exact
815 816 exact = True
816 817 dirignore = util.always # skip step 2
817 818 elif match.prefix(): # match.match, no patterns
818 819 skipstep3 = True
819 820
820 821 if not exact and self._checkcase:
821 822 normalize = self._normalize
822 823 normalizefile = self._normalizefile
823 824 skipstep3 = False
824 825 else:
825 826 normalize = self._normalize
826 827 normalizefile = None
827 828
828 829 # step 1: find all explicit files
829 830 results, work, dirsnotfound = self._walkexplicit(match, subrepos)
830 831
831 832 skipstep3 = skipstep3 and not (work or dirsnotfound)
832 833 work = [d for d in work if not dirignore(d[0])]
833 834
834 835 # step 2: visit subdirectories
835 836 def traverse(work, alreadynormed):
836 837 wadd = work.append
837 838 while work:
838 839 nd = work.pop()
839 840 skip = None
840 841 if nd == '.':
841 842 nd = ''
842 843 else:
843 844 skip = '.hg'
844 845 try:
845 846 entries = listdir(join(nd), stat=True, skip=skip)
846 847 except OSError as inst:
847 848 if inst.errno in (errno.EACCES, errno.ENOENT):
848 849 match.bad(self.pathto(nd), inst.strerror)
849 850 continue
850 851 raise
851 852 for f, kind, st in entries:
852 853 if normalizefile:
853 854 # even though f might be a directory, we're only
854 855 # interested in comparing it to files currently in the
855 856 # dmap -- therefore normalizefile is enough
856 857 nf = normalizefile(nd and (nd + "/" + f) or f, True,
857 858 True)
858 859 else:
859 860 nf = nd and (nd + "/" + f) or f
860 861 if nf not in results:
861 862 if kind == dirkind:
862 863 if not ignore(nf):
863 864 if matchtdir:
864 865 matchtdir(nf)
865 866 wadd(nf)
866 867 if nf in dmap and (matchalways or matchfn(nf)):
867 868 results[nf] = None
868 869 elif kind == regkind or kind == lnkkind:
869 870 if nf in dmap:
870 871 if matchalways or matchfn(nf):
871 872 results[nf] = st
872 873 elif ((matchalways or matchfn(nf))
873 874 and not ignore(nf)):
874 875 # unknown file -- normalize if necessary
875 876 if not alreadynormed:
876 877 nf = normalize(nf, False, True)
877 878 results[nf] = st
878 879 elif nf in dmap and (matchalways or matchfn(nf)):
879 880 results[nf] = None
880 881
881 882 for nd, d in work:
882 883 # alreadynormed means that processwork doesn't have to do any
883 884 # expensive directory normalization
884 885 alreadynormed = not normalize or nd == d
885 886 traverse([d], alreadynormed)
886 887
887 888 for s in subrepos:
888 889 del results[s]
889 890 del results['.hg']
890 891
891 892 # step 3: visit remaining files from dmap
892 893 if not skipstep3 and not exact:
893 894 # If a dmap file is not in results yet, it was either
894 895 # a) not matching matchfn b) ignored, c) missing, or d) under a
895 896 # symlink directory.
896 897 if not results and matchalways:
897 898 visit = dmap.keys()
898 899 else:
899 900 visit = [f for f in dmap if f not in results and matchfn(f)]
900 901 visit.sort()
901 902
902 903 if unknown:
903 904 # unknown == True means we walked all dirs under the roots
904 905 # that wasn't ignored, and everything that matched was stat'ed
905 906 # and is already in results.
906 907 # The rest must thus be ignored or under a symlink.
907 908 audit_path = pathutil.pathauditor(self._root)
908 909
909 910 for nf in iter(visit):
910 911 # If a stat for the same file was already added with a
911 912 # different case, don't add one for this, since that would
912 913 # make it appear as if the file exists under both names
913 914 # on disk.
914 915 if (normalizefile and
915 916 normalizefile(nf, True, True) in results):
916 917 results[nf] = None
917 918 # Report ignored items in the dmap as long as they are not
918 919 # under a symlink directory.
919 920 elif audit_path.check(nf):
920 921 try:
921 922 results[nf] = lstat(join(nf))
922 923 # file was just ignored, no links, and exists
923 924 except OSError:
924 925 # file doesn't exist
925 926 results[nf] = None
926 927 else:
927 928 # It's either missing or under a symlink directory
928 929 # which we in this case report as missing
929 930 results[nf] = None
930 931 else:
931 932 # We may not have walked the full directory tree above,
932 933 # so stat and check everything we missed.
933 934 nf = iter(visit).next
934 935 pos = 0
935 936 while pos < len(visit):
936 937 # visit in mid-sized batches so that we don't
937 938 # block signals indefinitely
938 939 xr = xrange(pos, min(len(visit), pos + 1000))
939 940 for st in util.statfiles([join(visit[n]) for n in xr]):
940 941 results[nf()] = st
941 942 pos += 1000
942 943 return results
943 944
944 945 def status(self, match, subrepos, ignored, clean, unknown):
945 946 '''Determine the status of the working copy relative to the
946 947 dirstate and return a pair of (unsure, status), where status is of type
947 948 scmutil.status and:
948 949
949 950 unsure:
950 951 files that might have been modified since the dirstate was
951 952 written, but need to be read to be sure (size is the same
952 953 but mtime differs)
953 954 status.modified:
954 955 files that have definitely been modified since the dirstate
955 956 was written (different size or mode)
956 957 status.clean:
957 958 files that have definitely not been modified since the
958 959 dirstate was written
959 960 '''
960 961 listignored, listclean, listunknown = ignored, clean, unknown
961 962 lookup, modified, added, unknown, ignored = [], [], [], [], []
962 963 removed, deleted, clean = [], [], []
963 964
964 965 dmap = self._map
965 966 ladd = lookup.append # aka "unsure"
966 967 madd = modified.append
967 968 aadd = added.append
968 969 uadd = unknown.append
969 970 iadd = ignored.append
970 971 radd = removed.append
971 972 dadd = deleted.append
972 973 cadd = clean.append
973 974 mexact = match.exact
974 975 dirignore = self._dirignore
975 976 checkexec = self._checkexec
976 977 copymap = self._copymap
977 978 lastnormaltime = self._lastnormaltime
978 979
979 980 # We need to do full walks when either
980 981 # - we're listing all clean files, or
981 982 # - match.traversedir does something, because match.traversedir should
982 983 # be called for every dir in the working dir
983 984 full = listclean or match.traversedir is not None
984 985 for fn, st in self.walk(match, subrepos, listunknown, listignored,
985 986 full=full).iteritems():
986 987 if fn not in dmap:
987 988 if (listignored or mexact(fn)) and dirignore(fn):
988 989 if listignored:
989 990 iadd(fn)
990 991 else:
991 992 uadd(fn)
992 993 continue
993 994
994 995 # This is equivalent to 'state, mode, size, time = dmap[fn]' but not
995 996 # written like that for performance reasons. dmap[fn] is not a
996 997 # Python tuple in compiled builds. The CPython UNPACK_SEQUENCE
997 998 # opcode has fast paths when the value to be unpacked is a tuple or
998 999 # a list, but falls back to creating a full-fledged iterator in
999 1000 # general. That is much slower than simply accessing and storing the
1000 1001 # tuple members one by one.
1001 1002 t = dmap[fn]
1002 1003 state = t[0]
1003 1004 mode = t[1]
1004 1005 size = t[2]
1005 1006 time = t[3]
1006 1007
1007 1008 if not st and state in "nma":
1008 1009 dadd(fn)
1009 1010 elif state == 'n':
1010 1011 mtime = util.statmtimesec(st)
1011 1012 if (size >= 0 and
1012 1013 ((size != st.st_size and size != st.st_size & _rangemask)
1013 1014 or ((mode ^ st.st_mode) & 0o100 and checkexec))
1014 1015 or size == -2 # other parent
1015 1016 or fn in copymap):
1016 1017 madd(fn)
1017 1018 elif time != mtime and time != mtime & _rangemask:
1018 1019 ladd(fn)
1019 1020 elif mtime == lastnormaltime:
1020 1021 # fn may have just been marked as normal and it may have
1021 1022 # changed in the same second without changing its size.
1022 1023 # This can happen if we quickly do multiple commits.
1023 1024 # Force lookup, so we don't miss such a racy file change.
1024 1025 ladd(fn)
1025 1026 elif listclean:
1026 1027 cadd(fn)
1027 1028 elif state == 'm':
1028 1029 madd(fn)
1029 1030 elif state == 'a':
1030 1031 aadd(fn)
1031 1032 elif state == 'r':
1032 1033 radd(fn)
1033 1034
1034 1035 return (lookup, scmutil.status(modified, added, removed, deleted,
1035 1036 unknown, ignored, clean))
1036 1037
1037 1038 def matches(self, match):
1038 1039 '''
1039 1040 return files in the dirstate (in whatever state) filtered by match
1040 1041 '''
1041 1042 dmap = self._map
1042 1043 if match.always():
1043 1044 return dmap.keys()
1044 1045 files = match.files()
1045 1046 if match.isexact():
1046 1047 # fast path -- filter the other way around, since typically files is
1047 1048 # much smaller than dmap
1048 1049 return [f for f in files if f in dmap]
1049 1050 if match.prefix() and all(fn in dmap for fn in files):
1050 1051 # fast path -- all the values are known to be files, so just return
1051 1052 # that
1052 1053 return list(files)
1053 1054 return [f for f in dmap if match(f)]
1054 1055
1056 def _actualfilename(self, repo):
1057 if repo.currenttransaction():
1058 return self._pendingfilename
1059 else:
1060 return self._filename
1061
1055 1062 def _savebackup(self, repo, suffix):
1056 1063 '''Save current dirstate into backup file with suffix'''
1057 self.write()
1058 filename = self._filename
1064 filename = self._actualfilename(repo)
1065
1066 # use '_writedirstate' instead of 'write' to write changes certainly,
1067 # because the latter omits writing out if transaction is running.
1068 # output file will be used to create backup of dirstate at this point.
1069 self._writedirstate(self._opener(filename, "w", atomictemp=True))
1070
1071 tr = repo.currenttransaction()
1072 if tr:
1073 # ensure that subsequent tr.writepending returns True for
1074 # changes written out above, even if dirstate is never
1075 # changed after this
1076 tr.addfilegenerator('dirstate', (self._filename,),
1077 self._writedirstate, location='plain')
1078
1079 # ensure that pending file written above is unlinked at
1080 # failure, even if tr.writepending isn't invoked until the
1081 # end of this transaction
1082 tr.registertmp(filename, location='plain')
1083
1059 1084 self._opener.write(filename + suffix, self._opener.tryread(filename))
1060 1085
1061 1086 def _restorebackup(self, repo, suffix):
1062 1087 '''Restore dirstate by backup file with suffix'''
1063 1088 # this "invalidate()" prevents "wlock.release()" from writing
1064 1089 # changes of dirstate out after restoring from backup file
1065 1090 self.invalidate()
1066 filename = self._filename
1091 filename = self._actualfilename(repo)
1067 1092 self._opener.rename(filename + suffix, filename)
1068 1093
1069 1094 def _clearbackup(self, repo, suffix):
1070 1095 '''Clear backup file with suffix'''
1071 filename = self._filename
1096 filename = self._actualfilename(repo)
1072 1097 self._opener.unlink(filename + suffix)
General Comments 0
You need to be logged in to leave comments. Login now