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