##// END OF EJS Templates
merge with stable
Matt Mackall -
r16326:b95b006e merge default
parent child Browse files
Show More
@@ -1,754 +1,755
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 import errno
8 8
9 9 from node import nullid
10 10 from i18n import _
11 11 import scmutil, util, ignore, osutil, parsers, encoding
12 12 import struct, os, stat, errno
13 13 import cStringIO
14 14
15 15 _format = ">cllll"
16 16 propertycache = util.propertycache
17 17 filecache = scmutil.filecache
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 def _finddirs(path):
30 30 pos = path.rfind('/')
31 31 while pos != -1:
32 32 yield path[:pos]
33 33 pos = path.rfind('/', 0, pos)
34 34
35 35 def _incdirs(dirs, path):
36 36 for base in _finddirs(path):
37 37 if base in dirs:
38 38 dirs[base] += 1
39 39 return
40 40 dirs[base] = 1
41 41
42 42 def _decdirs(dirs, path):
43 43 for base in _finddirs(path):
44 44 if dirs[base] > 1:
45 45 dirs[base] -= 1
46 46 return
47 47 del dirs[base]
48 48
49 49 class dirstate(object):
50 50
51 51 def __init__(self, opener, ui, root, validate):
52 52 '''Create a new dirstate object.
53 53
54 54 opener is an open()-like callable that can be used to open the
55 55 dirstate file; root is the root of the directory tracked by
56 56 the dirstate.
57 57 '''
58 58 self._opener = opener
59 59 self._validate = validate
60 60 self._root = root
61 61 self._rootdir = os.path.join(root, '')
62 62 self._dirty = False
63 63 self._dirtypl = False
64 64 self._lastnormaltime = 0
65 65 self._ui = ui
66 66 self._filecache = {}
67 67
68 68 @propertycache
69 69 def _map(self):
70 70 '''Return the dirstate contents as a map from filename to
71 71 (state, mode, size, time).'''
72 72 self._read()
73 73 return self._map
74 74
75 75 @propertycache
76 76 def _copymap(self):
77 77 self._read()
78 78 return self._copymap
79 79
80 80 @propertycache
81 81 def _foldmap(self):
82 82 f = {}
83 83 for name in self._map:
84 84 f[util.normcase(name)] = name
85 85 for name in self._dirs:
86 86 f[util.normcase(name)] = name
87 87 f['.'] = '.' # prevents useless util.fspath() invocation
88 88 return f
89 89
90 90 @repocache('branch')
91 91 def _branch(self):
92 92 try:
93 93 return self._opener.read("branch").strip() or "default"
94 94 except IOError, inst:
95 95 if inst.errno != errno.ENOENT:
96 96 raise
97 97 return "default"
98 98
99 99 @propertycache
100 100 def _pl(self):
101 101 try:
102 102 fp = self._opener("dirstate")
103 103 st = fp.read(40)
104 104 fp.close()
105 105 l = len(st)
106 106 if l == 40:
107 107 return st[:20], st[20:40]
108 108 elif l > 0 and l < 40:
109 109 raise util.Abort(_('working directory state appears damaged!'))
110 110 except IOError, err:
111 111 if err.errno != errno.ENOENT:
112 112 raise
113 113 return [nullid, nullid]
114 114
115 115 @propertycache
116 116 def _dirs(self):
117 117 dirs = {}
118 118 for f, s in self._map.iteritems():
119 119 if s[0] != 'r':
120 120 _incdirs(dirs, f)
121 121 return dirs
122 122
123 123 def dirs(self):
124 124 return self._dirs
125 125
126 126 @rootcache('.hgignore')
127 127 def _ignore(self):
128 128 files = [self._join('.hgignore')]
129 129 for name, path in self._ui.configitems("ui"):
130 130 if name == 'ignore' or name.startswith('ignore.'):
131 131 files.append(util.expandpath(path))
132 132 return ignore.ignore(self._root, files, self._ui.warn)
133 133
134 134 @propertycache
135 135 def _slash(self):
136 136 return self._ui.configbool('ui', 'slash') and os.sep != '/'
137 137
138 138 @propertycache
139 139 def _checklink(self):
140 140 return util.checklink(self._root)
141 141
142 142 @propertycache
143 143 def _checkexec(self):
144 144 return util.checkexec(self._root)
145 145
146 146 @propertycache
147 147 def _checkcase(self):
148 148 return not util.checkcase(self._join('.hg'))
149 149
150 150 def _join(self, f):
151 151 # much faster than os.path.join()
152 152 # it's safe because f is always a relative path
153 153 return self._rootdir + f
154 154
155 155 def flagfunc(self, buildfallback):
156 156 if self._checklink and self._checkexec:
157 157 def f(x):
158 158 p = self._join(x)
159 159 if os.path.islink(p):
160 160 return 'l'
161 161 if util.isexec(p):
162 162 return 'x'
163 163 return ''
164 164 return f
165 165
166 166 fallback = buildfallback()
167 167 if self._checklink:
168 168 def f(x):
169 169 if os.path.islink(self._join(x)):
170 170 return 'l'
171 171 if 'x' in fallback(x):
172 172 return 'x'
173 173 return ''
174 174 return f
175 175 if self._checkexec:
176 176 def f(x):
177 177 if 'l' in fallback(x):
178 178 return 'l'
179 179 if util.isexec(self._join(x)):
180 180 return 'x'
181 181 return ''
182 182 return f
183 183 else:
184 184 return fallback
185 185
186 186 def getcwd(self):
187 187 cwd = os.getcwd()
188 188 if cwd == self._root:
189 189 return ''
190 190 # self._root ends with a path separator if self._root is '/' or 'C:\'
191 191 rootsep = self._root
192 192 if not util.endswithsep(rootsep):
193 193 rootsep += os.sep
194 194 if cwd.startswith(rootsep):
195 195 return cwd[len(rootsep):]
196 196 else:
197 197 # we're outside the repo. return an absolute path.
198 198 return cwd
199 199
200 200 def pathto(self, f, cwd=None):
201 201 if cwd is None:
202 202 cwd = self.getcwd()
203 203 path = util.pathto(self._root, cwd, f)
204 204 if self._slash:
205 205 return util.normpath(path)
206 206 return path
207 207
208 208 def __getitem__(self, key):
209 209 '''Return the current state of key (a filename) in the dirstate.
210 210
211 211 States are:
212 212 n normal
213 213 m needs merging
214 214 r marked for removal
215 215 a marked for addition
216 216 ? not tracked
217 217 '''
218 218 return self._map.get(key, ("?",))[0]
219 219
220 220 def __contains__(self, key):
221 221 return key in self._map
222 222
223 223 def __iter__(self):
224 224 for x in sorted(self._map):
225 225 yield x
226 226
227 227 def parents(self):
228 228 return [self._validate(p) for p in self._pl]
229 229
230 230 def p1(self):
231 231 return self._validate(self._pl[0])
232 232
233 233 def p2(self):
234 234 return self._validate(self._pl[1])
235 235
236 236 def branch(self):
237 237 return encoding.tolocal(self._branch)
238 238
239 239 def setparents(self, p1, p2=nullid):
240 240 self._dirty = self._dirtypl = True
241 241 self._pl = p1, p2
242 242
243 243 def setbranch(self, branch):
244 244 if branch in ['tip', '.', 'null']:
245 245 raise util.Abort(_('the name \'%s\' is reserved') % branch)
246 246 self._branch = encoding.fromlocal(branch)
247 247 self._opener.write("branch", self._branch + '\n')
248 248
249 249 def _read(self):
250 250 self._map = {}
251 251 self._copymap = {}
252 252 try:
253 253 st = self._opener.read("dirstate")
254 254 except IOError, err:
255 255 if err.errno != errno.ENOENT:
256 256 raise
257 257 return
258 258 if not st:
259 259 return
260 260
261 261 p = parsers.parse_dirstate(self._map, self._copymap, st)
262 262 if not self._dirtypl:
263 263 self._pl = p
264 264
265 265 def invalidate(self):
266 266 for a in ("_map", "_copymap", "_foldmap", "_branch", "_pl", "_dirs",
267 267 "_ignore"):
268 268 if a in self.__dict__:
269 269 delattr(self, a)
270 270 self._lastnormaltime = 0
271 271 self._dirty = False
272 272
273 273 def copy(self, source, dest):
274 274 """Mark dest as a copy of source. Unmark dest if source is None."""
275 275 if source == dest:
276 276 return
277 277 self._dirty = True
278 278 if source is not None:
279 279 self._copymap[dest] = source
280 280 elif dest in self._copymap:
281 281 del self._copymap[dest]
282 282
283 283 def copied(self, file):
284 284 return self._copymap.get(file, None)
285 285
286 286 def copies(self):
287 287 return self._copymap
288 288
289 289 def _droppath(self, f):
290 290 if self[f] not in "?r" and "_dirs" in self.__dict__:
291 291 _decdirs(self._dirs, f)
292 292
293 293 def _addpath(self, f, check=False):
294 294 oldstate = self[f]
295 295 if check or oldstate == "r":
296 296 scmutil.checkfilename(f)
297 297 if f in self._dirs:
298 298 raise util.Abort(_('directory %r already in dirstate') % f)
299 299 # shadows
300 300 for d in _finddirs(f):
301 301 if d in self._dirs:
302 302 break
303 303 if d in self._map and self[d] != 'r':
304 304 raise util.Abort(
305 305 _('file %r in dirstate clashes with %r') % (d, f))
306 306 if oldstate in "?r" and "_dirs" in self.__dict__:
307 307 _incdirs(self._dirs, f)
308 308
309 309 def normal(self, f):
310 310 '''Mark a file normal and clean.'''
311 311 self._dirty = True
312 312 self._addpath(f)
313 313 s = os.lstat(self._join(f))
314 314 mtime = int(s.st_mtime)
315 315 self._map[f] = ('n', s.st_mode, s.st_size, mtime)
316 316 if f in self._copymap:
317 317 del self._copymap[f]
318 318 if mtime > self._lastnormaltime:
319 319 # Remember the most recent modification timeslot for status(),
320 320 # to make sure we won't miss future size-preserving file content
321 321 # modifications that happen within the same timeslot.
322 322 self._lastnormaltime = mtime
323 323
324 324 def normallookup(self, f):
325 325 '''Mark a file normal, but possibly dirty.'''
326 326 if self._pl[1] != nullid and f in self._map:
327 327 # if there is a merge going on and the file was either
328 328 # in state 'm' (-1) or coming from other parent (-2) before
329 329 # being removed, restore that state.
330 330 entry = self._map[f]
331 331 if entry[0] == 'r' and entry[2] in (-1, -2):
332 332 source = self._copymap.get(f)
333 333 if entry[2] == -1:
334 334 self.merge(f)
335 335 elif entry[2] == -2:
336 336 self.otherparent(f)
337 337 if source:
338 338 self.copy(source, f)
339 339 return
340 340 if entry[0] == 'm' or entry[0] == 'n' and entry[2] == -2:
341 341 return
342 342 self._dirty = True
343 343 self._addpath(f)
344 344 self._map[f] = ('n', 0, -1, -1)
345 345 if f in self._copymap:
346 346 del self._copymap[f]
347 347
348 348 def otherparent(self, f):
349 349 '''Mark as coming from the other parent, always dirty.'''
350 350 if self._pl[1] == nullid:
351 351 raise util.Abort(_("setting %r to other parent "
352 352 "only allowed in merges") % f)
353 353 self._dirty = True
354 354 self._addpath(f)
355 355 self._map[f] = ('n', 0, -2, -1)
356 356 if f in self._copymap:
357 357 del self._copymap[f]
358 358
359 359 def add(self, f):
360 360 '''Mark a file added.'''
361 361 self._dirty = True
362 362 self._addpath(f, True)
363 363 self._map[f] = ('a', 0, -1, -1)
364 364 if f in self._copymap:
365 365 del self._copymap[f]
366 366
367 367 def remove(self, f):
368 368 '''Mark a file removed.'''
369 369 self._dirty = True
370 370 self._droppath(f)
371 371 size = 0
372 372 if self._pl[1] != nullid and f in self._map:
373 373 # backup the previous state
374 374 entry = self._map[f]
375 375 if entry[0] == 'm': # merge
376 376 size = -1
377 377 elif entry[0] == 'n' and entry[2] == -2: # other parent
378 378 size = -2
379 379 self._map[f] = ('r', 0, size, 0)
380 380 if size == 0 and f in self._copymap:
381 381 del self._copymap[f]
382 382
383 383 def merge(self, f):
384 384 '''Mark a file merged.'''
385 385 self._dirty = True
386 386 s = os.lstat(self._join(f))
387 387 self._addpath(f)
388 388 self._map[f] = ('m', s.st_mode, s.st_size, int(s.st_mtime))
389 389 if f in self._copymap:
390 390 del self._copymap[f]
391 391
392 392 def drop(self, f):
393 393 '''Drop a file from the dirstate'''
394 394 if f in self._map:
395 395 self._dirty = True
396 396 self._droppath(f)
397 397 del self._map[f]
398 398
399 399 def _normalize(self, path, isknown):
400 400 normed = util.normcase(path)
401 401 folded = self._foldmap.get(normed, None)
402 402 if folded is None:
403 403 if isknown or not os.path.lexists(os.path.join(self._root, path)):
404 404 folded = path
405 405 else:
406 406 # recursively normalize leading directory components
407 407 # against dirstate
408 408 if '/' in normed:
409 d, f = normed.rsplit('/')
410 d = self._root + "/" + self._normalize(d, isknown)
411 folded = d + "/" + util.fspath(f, d)
409 d, f = normed.rsplit('/', 1)
410 d = self._normalize(d, isknown)
411 r = self._root + "/" + d
412 folded = d + "/" + util.fspath(f, r)
412 413 else:
413 414 folded = util.fspath(normed, self._root)
414 415 self._foldmap[normed] = folded
415 416
416 417 return folded
417 418
418 419 def normalize(self, path, isknown=False):
419 420 '''
420 421 normalize the case of a pathname when on a casefolding filesystem
421 422
422 423 isknown specifies whether the filename came from walking the
423 424 disk, to avoid extra filesystem access
424 425
425 426 The normalized case is determined based on the following precedence:
426 427
427 428 - version of name already stored in the dirstate
428 429 - version of name stored on disk
429 430 - version provided via command arguments
430 431 '''
431 432
432 433 if self._checkcase:
433 434 return self._normalize(path, isknown)
434 435 return path
435 436
436 437 def clear(self):
437 438 self._map = {}
438 439 if "_dirs" in self.__dict__:
439 440 delattr(self, "_dirs")
440 441 self._copymap = {}
441 442 self._pl = [nullid, nullid]
442 443 self._lastnormaltime = 0
443 444 self._dirty = True
444 445
445 446 def rebuild(self, parent, files):
446 447 self.clear()
447 448 for f in files:
448 449 if 'x' in files.flags(f):
449 450 self._map[f] = ('n', 0777, -1, 0)
450 451 else:
451 452 self._map[f] = ('n', 0666, -1, 0)
452 453 self._pl = (parent, nullid)
453 454 self._dirty = True
454 455
455 456 def write(self):
456 457 if not self._dirty:
457 458 return
458 459 st = self._opener("dirstate", "w", atomictemp=True)
459 460
460 461 # use the modification time of the newly created temporary file as the
461 462 # filesystem's notion of 'now'
462 463 now = int(util.fstat(st).st_mtime)
463 464
464 465 cs = cStringIO.StringIO()
465 466 copymap = self._copymap
466 467 pack = struct.pack
467 468 write = cs.write
468 469 write("".join(self._pl))
469 470 for f, e in self._map.iteritems():
470 471 if e[0] == 'n' and e[3] == now:
471 472 # The file was last modified "simultaneously" with the current
472 473 # write to dirstate (i.e. within the same second for file-
473 474 # systems with a granularity of 1 sec). This commonly happens
474 475 # for at least a couple of files on 'update'.
475 476 # The user could change the file without changing its size
476 477 # within the same second. Invalidate the file's stat data in
477 478 # dirstate, forcing future 'status' calls to compare the
478 479 # contents of the file. This prevents mistakenly treating such
479 480 # files as clean.
480 481 e = (e[0], 0, -1, -1) # mark entry as 'unset'
481 482 self._map[f] = e
482 483
483 484 if f in copymap:
484 485 f = "%s\0%s" % (f, copymap[f])
485 486 e = pack(_format, e[0], e[1], e[2], e[3], len(f))
486 487 write(e)
487 488 write(f)
488 489 st.write(cs.getvalue())
489 490 st.close()
490 491 self._lastnormaltime = 0
491 492 self._dirty = self._dirtypl = False
492 493
493 494 def _dirignore(self, f):
494 495 if f == '.':
495 496 return False
496 497 if self._ignore(f):
497 498 return True
498 499 for p in _finddirs(f):
499 500 if self._ignore(p):
500 501 return True
501 502 return False
502 503
503 504 def walk(self, match, subrepos, unknown, ignored):
504 505 '''
505 506 Walk recursively through the directory tree, finding all files
506 507 matched by match.
507 508
508 509 Return a dict mapping filename to stat-like object (either
509 510 mercurial.osutil.stat instance or return value of os.stat()).
510 511 '''
511 512
512 513 def fwarn(f, msg):
513 514 self._ui.warn('%s: %s\n' % (self.pathto(f), msg))
514 515 return False
515 516
516 517 def badtype(mode):
517 518 kind = _('unknown')
518 519 if stat.S_ISCHR(mode):
519 520 kind = _('character device')
520 521 elif stat.S_ISBLK(mode):
521 522 kind = _('block device')
522 523 elif stat.S_ISFIFO(mode):
523 524 kind = _('fifo')
524 525 elif stat.S_ISSOCK(mode):
525 526 kind = _('socket')
526 527 elif stat.S_ISDIR(mode):
527 528 kind = _('directory')
528 529 return _('unsupported file type (type is %s)') % kind
529 530
530 531 ignore = self._ignore
531 532 dirignore = self._dirignore
532 533 if ignored:
533 534 ignore = util.never
534 535 dirignore = util.never
535 536 elif not unknown:
536 537 # if unknown and ignored are False, skip step 2
537 538 ignore = util.always
538 539 dirignore = util.always
539 540
540 541 matchfn = match.matchfn
541 542 badfn = match.bad
542 543 dmap = self._map
543 544 normpath = util.normpath
544 545 listdir = osutil.listdir
545 546 lstat = os.lstat
546 547 getkind = stat.S_IFMT
547 548 dirkind = stat.S_IFDIR
548 549 regkind = stat.S_IFREG
549 550 lnkkind = stat.S_IFLNK
550 551 join = self._join
551 552 work = []
552 553 wadd = work.append
553 554
554 555 exact = skipstep3 = False
555 556 if matchfn == match.exact: # match.exact
556 557 exact = True
557 558 dirignore = util.always # skip step 2
558 559 elif match.files() and not match.anypats(): # match.match, no patterns
559 560 skipstep3 = True
560 561
561 562 if not exact and self._checkcase:
562 563 normalize = self._normalize
563 564 skipstep3 = False
564 565 else:
565 566 normalize = lambda x, y: x
566 567
567 568 files = sorted(match.files())
568 569 subrepos.sort()
569 570 i, j = 0, 0
570 571 while i < len(files) and j < len(subrepos):
571 572 subpath = subrepos[j] + "/"
572 573 if files[i] < subpath:
573 574 i += 1
574 575 continue
575 576 while i < len(files) and files[i].startswith(subpath):
576 577 del files[i]
577 578 j += 1
578 579
579 580 if not files or '.' in files:
580 581 files = ['']
581 582 results = dict.fromkeys(subrepos)
582 583 results['.hg'] = None
583 584
584 585 # step 1: find all explicit files
585 586 for ff in files:
586 587 nf = normalize(normpath(ff), False)
587 588 if nf in results:
588 589 continue
589 590
590 591 try:
591 592 st = lstat(join(nf))
592 593 kind = getkind(st.st_mode)
593 594 if kind == dirkind:
594 595 skipstep3 = False
595 596 if nf in dmap:
596 597 #file deleted on disk but still in dirstate
597 598 results[nf] = None
598 599 match.dir(nf)
599 600 if not dirignore(nf):
600 601 wadd(nf)
601 602 elif kind == regkind or kind == lnkkind:
602 603 results[nf] = st
603 604 else:
604 605 badfn(ff, badtype(kind))
605 606 if nf in dmap:
606 607 results[nf] = None
607 608 except OSError, inst:
608 609 if nf in dmap: # does it exactly match a file?
609 610 results[nf] = None
610 611 else: # does it match a directory?
611 612 prefix = nf + "/"
612 613 for fn in dmap:
613 614 if fn.startswith(prefix):
614 615 match.dir(nf)
615 616 skipstep3 = False
616 617 break
617 618 else:
618 619 badfn(ff, inst.strerror)
619 620
620 621 # step 2: visit subdirectories
621 622 while work:
622 623 nd = work.pop()
623 624 skip = None
624 625 if nd == '.':
625 626 nd = ''
626 627 else:
627 628 skip = '.hg'
628 629 try:
629 630 entries = listdir(join(nd), stat=True, skip=skip)
630 631 except OSError, inst:
631 632 if inst.errno == errno.EACCES:
632 633 fwarn(nd, inst.strerror)
633 634 continue
634 635 raise
635 636 for f, kind, st in entries:
636 637 nf = normalize(nd and (nd + "/" + f) or f, True)
637 638 if nf not in results:
638 639 if kind == dirkind:
639 640 if not ignore(nf):
640 641 match.dir(nf)
641 642 wadd(nf)
642 643 if nf in dmap and matchfn(nf):
643 644 results[nf] = None
644 645 elif kind == regkind or kind == lnkkind:
645 646 if nf in dmap:
646 647 if matchfn(nf):
647 648 results[nf] = st
648 649 elif matchfn(nf) and not ignore(nf):
649 650 results[nf] = st
650 651 elif nf in dmap and matchfn(nf):
651 652 results[nf] = None
652 653
653 654 # step 3: report unseen items in the dmap hash
654 655 if not skipstep3 and not exact:
655 656 visit = sorted([f for f in dmap if f not in results and matchfn(f)])
656 657 for nf, st in zip(visit, util.statfiles([join(i) for i in visit])):
657 658 if not st is None and not getkind(st.st_mode) in (regkind, lnkkind):
658 659 st = None
659 660 results[nf] = st
660 661 for s in subrepos:
661 662 del results[s]
662 663 del results['.hg']
663 664 return results
664 665
665 666 def status(self, match, subrepos, ignored, clean, unknown):
666 667 '''Determine the status of the working copy relative to the
667 668 dirstate and return a tuple of lists (unsure, modified, added,
668 669 removed, deleted, unknown, ignored, clean), where:
669 670
670 671 unsure:
671 672 files that might have been modified since the dirstate was
672 673 written, but need to be read to be sure (size is the same
673 674 but mtime differs)
674 675 modified:
675 676 files that have definitely been modified since the dirstate
676 677 was written (different size or mode)
677 678 added:
678 679 files that have been explicitly added with hg add
679 680 removed:
680 681 files that have been explicitly removed with hg remove
681 682 deleted:
682 683 files that have been deleted through other means ("missing")
683 684 unknown:
684 685 files not in the dirstate that are not ignored
685 686 ignored:
686 687 files not in the dirstate that are ignored
687 688 (by _dirignore())
688 689 clean:
689 690 files that have definitely not been modified since the
690 691 dirstate was written
691 692 '''
692 693 listignored, listclean, listunknown = ignored, clean, unknown
693 694 lookup, modified, added, unknown, ignored = [], [], [], [], []
694 695 removed, deleted, clean = [], [], []
695 696
696 697 dmap = self._map
697 698 ladd = lookup.append # aka "unsure"
698 699 madd = modified.append
699 700 aadd = added.append
700 701 uadd = unknown.append
701 702 iadd = ignored.append
702 703 radd = removed.append
703 704 dadd = deleted.append
704 705 cadd = clean.append
705 706
706 707 lnkkind = stat.S_IFLNK
707 708
708 709 for fn, st in self.walk(match, subrepos, listunknown,
709 710 listignored).iteritems():
710 711 if fn not in dmap:
711 712 if (listignored or match.exact(fn)) and self._dirignore(fn):
712 713 if listignored:
713 714 iadd(fn)
714 715 elif listunknown:
715 716 uadd(fn)
716 717 continue
717 718
718 719 state, mode, size, time = dmap[fn]
719 720
720 721 if not st and state in "nma":
721 722 dadd(fn)
722 723 elif state == 'n':
723 724 # The "mode & lnkkind != lnkkind or self._checklink"
724 725 # lines are an expansion of "islink => checklink"
725 726 # where islink means "is this a link?" and checklink
726 727 # means "can we check links?".
727 728 mtime = int(st.st_mtime)
728 729 if (size >= 0 and
729 730 (size != st.st_size
730 731 or ((mode ^ st.st_mode) & 0100 and self._checkexec))
731 732 and (mode & lnkkind != lnkkind or self._checklink)
732 733 or size == -2 # other parent
733 734 or fn in self._copymap):
734 735 madd(fn)
735 736 elif (mtime != time
736 737 and (mode & lnkkind != lnkkind or self._checklink)):
737 738 ladd(fn)
738 739 elif mtime == self._lastnormaltime:
739 740 # fn may have been changed in the same timeslot without
740 741 # changing its size. This can happen if we quickly do
741 742 # multiple commits in a single transaction.
742 743 # Force lookup, so we don't miss such a racy file change.
743 744 ladd(fn)
744 745 elif listclean:
745 746 cadd(fn)
746 747 elif state == 'm':
747 748 madd(fn)
748 749 elif state == 'a':
749 750 aadd(fn)
750 751 elif state == 'r':
751 752 radd(fn)
752 753
753 754 return (lookup, modified, added, removed, deleted, unknown, ignored,
754 755 clean)
@@ -1,329 +1,345
1 1 #!/usr/bin/env python
2 2 """Test the running system for features availability. Exit with zero
3 3 if all features are there, non-zero otherwise. If a feature name is
4 4 prefixed with "no-", the absence of feature is tested.
5 5 """
6 6 import optparse
7 import os
7 import os, stat
8 8 import re
9 9 import sys
10 10 import tempfile
11 11
12 12 tempprefix = 'hg-hghave-'
13 13
14 14 def matchoutput(cmd, regexp, ignorestatus=False):
15 15 """Return True if cmd executes successfully and its output
16 16 is matched by the supplied regular expression.
17 17 """
18 18 r = re.compile(regexp)
19 19 fh = os.popen(cmd)
20 20 s = fh.read()
21 21 try:
22 22 ret = fh.close()
23 23 except IOError:
24 24 # Happen in Windows test environment
25 25 ret = 1
26 26 return (ignorestatus or ret is None) and r.search(s)
27 27
28 28 def has_baz():
29 29 return matchoutput('baz --version 2>&1', r'baz Bazaar version')
30 30
31 31 def has_bzr():
32 32 try:
33 33 import bzrlib
34 34 return bzrlib.__doc__ != None
35 35 except ImportError:
36 36 return False
37 37
38 38 def has_bzr114():
39 39 try:
40 40 import bzrlib
41 41 return (bzrlib.__doc__ != None
42 42 and bzrlib.version_info[:2] >= (1, 14))
43 43 except ImportError:
44 44 return False
45 45
46 46 def has_cvs():
47 47 re = r'Concurrent Versions System.*?server'
48 48 return matchoutput('cvs --version 2>&1', re) and not has_msys()
49 49
50 50 def has_darcs():
51 51 return matchoutput('darcs --version', r'2\.[2-9]', True)
52 52
53 53 def has_mtn():
54 54 return matchoutput('mtn --version', r'monotone', True) and not matchoutput(
55 55 'mtn --version', r'monotone 0\.', True)
56 56
57 57 def has_eol_in_paths():
58 58 try:
59 59 fd, path = tempfile.mkstemp(prefix=tempprefix, suffix='\n\r')
60 60 os.close(fd)
61 61 os.remove(path)
62 62 return True
63 63 except:
64 64 return False
65 65
66 66 def has_executablebit():
67 fd, path = tempfile.mkstemp(prefix=tempprefix)
68 os.close(fd)
67 try:
68 EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
69 fh, fn = tempfile.mkstemp(dir=".", prefix='hg-checkexec-')
69 70 try:
70 s = os.lstat(path).st_mode
71 os.chmod(path, s | 0100)
72 return (os.lstat(path).st_mode & 0100 != 0)
71 os.close(fh)
72 m = os.stat(fn).st_mode & 0777
73 new_file_has_exec = m & EXECFLAGS
74 os.chmod(fn, m ^ EXECFLAGS)
75 exec_flags_cannot_flip = ((os.stat(fn).st_mode & 0777) == m)
73 76 finally:
74 os.remove(path)
77 os.unlink(fn)
78 except (IOError, OSError):
79 # we don't care, the user probably won't be able to commit anyway
80 return False
81 return not (new_file_has_exec or exec_flags_cannot_flip)
75 82
76 83 def has_icasefs():
77 84 # Stolen from mercurial.util
78 85 fd, path = tempfile.mkstemp(prefix=tempprefix, dir='.')
79 86 os.close(fd)
80 87 try:
81 88 s1 = os.stat(path)
82 89 d, b = os.path.split(path)
83 90 p2 = os.path.join(d, b.upper())
84 91 if path == p2:
85 92 p2 = os.path.join(d, b.lower())
86 93 try:
87 94 s2 = os.stat(p2)
88 95 return s2 == s1
89 96 except:
90 97 return False
91 98 finally:
92 99 os.remove(path)
93 100
94 101 def has_inotify():
95 102 try:
96 103 import hgext.inotify.linux.watcher
97 104 return True
98 105 except ImportError:
99 106 return False
100 107
101 108 def has_fifo():
102 109 return hasattr(os, "mkfifo")
103 110
104 111 def has_cacheable_fs():
105 112 from mercurial import util
106 113
107 114 fd, path = tempfile.mkstemp(prefix=tempprefix)
108 115 os.close(fd)
109 116 try:
110 117 return util.cachestat(path).cacheable()
111 118 finally:
112 119 os.remove(path)
113 120
114 121 def has_lsprof():
115 122 try:
116 123 import _lsprof
117 124 return True
118 125 except ImportError:
119 126 return False
120 127
121 128 def has_gettext():
122 129 return matchoutput('msgfmt --version', 'GNU gettext-tools')
123 130
124 131 def has_git():
125 132 return matchoutput('git --version 2>&1', r'^git version')
126 133
127 134 def has_docutils():
128 135 try:
129 136 from docutils.core import publish_cmdline
130 137 return True
131 138 except ImportError:
132 139 return False
133 140
134 141 def getsvnversion():
135 142 m = matchoutput('svn --version 2>&1', r'^svn,\s+version\s+(\d+)\.(\d+)')
136 143 if not m:
137 144 return (0, 0)
138 145 return (int(m.group(1)), int(m.group(2)))
139 146
140 147 def has_svn15():
141 148 return getsvnversion() >= (1, 5)
142 149
143 150 def has_svn13():
144 151 return getsvnversion() >= (1, 3)
145 152
146 153 def has_svn():
147 154 return matchoutput('svn --version 2>&1', r'^svn, version') and \
148 155 matchoutput('svnadmin --version 2>&1', r'^svnadmin, version')
149 156
150 157 def has_svn_bindings():
151 158 try:
152 159 import svn.core
153 160 version = svn.core.SVN_VER_MAJOR, svn.core.SVN_VER_MINOR
154 161 if version < (1, 4):
155 162 return False
156 163 return True
157 164 except ImportError:
158 165 return False
159 166
160 167 def has_p4():
161 168 return matchoutput('p4 -V', r'Rev\. P4/') and matchoutput('p4d -V', r'Rev\. P4D/')
162 169
163 170 def has_symlink():
171 if not hasattr(os, "symlink"):
172 return False
173 name = tempfile.mktemp(dir=".", prefix='hg-checklink-')
174 try:
175 os.symlink(".", name)
176 os.unlink(name)
177 return True
178 except (OSError, AttributeError):
179 return False
164 180 return hasattr(os, "symlink") # FIXME: should also check file system and os
165 181
166 182 def has_tla():
167 183 return matchoutput('tla --version 2>&1', r'The GNU Arch Revision')
168 184
169 185 def has_gpg():
170 186 return matchoutput('gpg --version 2>&1', r'GnuPG')
171 187
172 188 def has_unix_permissions():
173 189 d = tempfile.mkdtemp(prefix=tempprefix, dir=".")
174 190 try:
175 191 fname = os.path.join(d, 'foo')
176 192 for umask in (077, 007, 022):
177 193 os.umask(umask)
178 194 f = open(fname, 'w')
179 195 f.close()
180 196 mode = os.stat(fname).st_mode
181 197 os.unlink(fname)
182 198 if mode & 0777 != ~umask & 0666:
183 199 return False
184 200 return True
185 201 finally:
186 202 os.rmdir(d)
187 203
188 204 def has_pyflakes():
189 205 return matchoutput('echo "import re" 2>&1 | pyflakes',
190 206 r"<stdin>:1: 're' imported but unused",
191 207 True)
192 208
193 209 def has_pygments():
194 210 try:
195 211 import pygments
196 212 return True
197 213 except ImportError:
198 214 return False
199 215
200 216 def has_outer_repo():
201 217 return matchoutput('hg root 2>&1', r'')
202 218
203 219 def has_ssl():
204 220 try:
205 221 import ssl
206 222 import OpenSSL
207 223 OpenSSL.SSL.Context
208 224 return True
209 225 except ImportError:
210 226 return False
211 227
212 228 def has_windows():
213 229 return os.name == 'nt'
214 230
215 231 def has_system_sh():
216 232 return os.name != 'nt'
217 233
218 234 def has_serve():
219 235 return os.name != 'nt' # gross approximation
220 236
221 237 def has_tic():
222 238 return matchoutput('test -x "`which tic`"', '')
223 239
224 240 def has_msys():
225 241 return os.getenv('MSYSTEM')
226 242
227 243 checks = {
228 244 "baz": (has_baz, "GNU Arch baz client"),
229 245 "bzr": (has_bzr, "Canonical's Bazaar client"),
230 246 "bzr114": (has_bzr114, "Canonical's Bazaar client >= 1.14"),
231 247 "cacheable": (has_cacheable_fs, "cacheable filesystem"),
232 248 "cvs": (has_cvs, "cvs client/server"),
233 249 "darcs": (has_darcs, "darcs client"),
234 250 "docutils": (has_docutils, "Docutils text processing library"),
235 251 "eol-in-paths": (has_eol_in_paths, "end-of-lines in paths"),
236 252 "execbit": (has_executablebit, "executable bit"),
237 253 "fifo": (has_fifo, "named pipes"),
238 254 "gettext": (has_gettext, "GNU Gettext (msgfmt)"),
239 255 "git": (has_git, "git command line client"),
240 256 "gpg": (has_gpg, "gpg client"),
241 257 "icasefs": (has_icasefs, "case insensitive file system"),
242 258 "inotify": (has_inotify, "inotify extension support"),
243 259 "lsprof": (has_lsprof, "python lsprof module"),
244 260 "mtn": (has_mtn, "monotone client (>= 1.0)"),
245 261 "outer-repo": (has_outer_repo, "outer repo"),
246 262 "p4": (has_p4, "Perforce server and client"),
247 263 "pyflakes": (has_pyflakes, "Pyflakes python linter"),
248 264 "pygments": (has_pygments, "Pygments source highlighting library"),
249 265 "serve": (has_serve, "platform and python can manage 'hg serve -d'"),
250 266 "ssl": (has_ssl, "python >= 2.6 ssl module and python OpenSSL"),
251 267 "svn": (has_svn, "subversion client and admin tools"),
252 268 "svn13": (has_svn13, "subversion client and admin tools >= 1.3"),
253 269 "svn15": (has_svn15, "subversion client and admin tools >= 1.5"),
254 270 "svn-bindings": (has_svn_bindings, "subversion python bindings"),
255 271 "symlink": (has_symlink, "symbolic links"),
256 272 "system-sh": (has_system_sh, "system() uses sh"),
257 273 "tic": (has_tic, "terminfo compiler"),
258 274 "tla": (has_tla, "GNU Arch tla client"),
259 275 "unix-permissions": (has_unix_permissions, "unix-style permissions"),
260 276 "windows": (has_windows, "Windows"),
261 277 "msys": (has_msys, "Windows with MSYS"),
262 278 }
263 279
264 280 def list_features():
265 281 for name, feature in checks.iteritems():
266 282 desc = feature[1]
267 283 print name + ':', desc
268 284
269 285 def test_features():
270 286 failed = 0
271 287 for name, feature in checks.iteritems():
272 288 check, _ = feature
273 289 try:
274 290 check()
275 291 except Exception, e:
276 292 print "feature %s failed: %s" % (name, e)
277 293 failed += 1
278 294 return failed
279 295
280 296 parser = optparse.OptionParser("%prog [options] [features]")
281 297 parser.add_option("--test-features", action="store_true",
282 298 help="test available features")
283 299 parser.add_option("--list-features", action="store_true",
284 300 help="list available features")
285 301 parser.add_option("-q", "--quiet", action="store_true",
286 302 help="check features silently")
287 303
288 304 if __name__ == '__main__':
289 305 options, args = parser.parse_args()
290 306 if options.list_features:
291 307 list_features()
292 308 sys.exit(0)
293 309
294 310 if options.test_features:
295 311 sys.exit(test_features())
296 312
297 313 quiet = options.quiet
298 314
299 315 failures = 0
300 316
301 317 def error(msg):
302 318 global failures
303 319 if not quiet:
304 320 sys.stderr.write(msg + '\n')
305 321 failures += 1
306 322
307 323 for feature in args:
308 324 negate = feature.startswith('no-')
309 325 if negate:
310 326 feature = feature[3:]
311 327
312 328 if feature not in checks:
313 329 error('skipped: unknown feature: ' + feature)
314 330 continue
315 331
316 332 check, desc = checks[feature]
317 333 try:
318 334 available = check()
319 335 except Exception, e:
320 336 error('hghave check failed: ' + feature)
321 337 continue
322 338
323 339 if not negate and not available:
324 340 error('skipped: missing feature: ' + desc)
325 341 elif negate and available:
326 342 error('skipped: system supports %s' % desc)
327 343
328 344 if failures != 0:
329 345 sys.exit(1)
@@ -1,116 +1,127
1 1 $ "$TESTDIR/hghave" icasefs || exit 80
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 a
28 28 committed changeset 0:07f4944404050f47db2e5c5071e0e84e7a27bba9
29 29
30 30 Case-changing renames should work:
31 31
32 32 $ hg mv a A
33 33 $ hg mv A a
34 34 $ hg st
35 35 $ cd ..
36 36
37 37 test case collision between revisions (issue912)
38 38
39 39 $ hg init repo3
40 40 $ cd repo3
41 41 $ echo a > a
42 42 $ hg ci -Am adda
43 43 adding a
44 44 $ hg rm a
45 45 $ hg ci -Am removea
46 46 $ echo A > A
47 47
48 48 on linux hfs keeps the old case stored, force it
49 49
50 50 $ mv a aa
51 51 $ mv aa A
52 52 $ hg ci -Am addA
53 53 adding A
54 54
55 55 used to fail under case insensitive fs
56 56
57 57 $ hg up -C 0
58 58 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
59 59 $ hg up -C
60 60 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
61 61
62 62 no clobbering of untracked files with wrong casing
63 63
64 64 $ hg up -r null
65 65 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
66 66 $ echo gold > a
67 67 $ hg up
68 68 A: untracked file differs
69 69 abort: untracked files in working directory differ from files in requested revision
70 70 [255]
71 71 $ cat a
72 72 gold
73 73
74 74 $ cd ..
75 75
76 issue 3342: file in nested directory causes unexpected abort
77
78 $ hg init issue3342
79 $ cd issue3342
80
81 $ mkdir -p a/B/c/D
82 $ echo e > a/B/c/D/e
83 $ hg add a/B/c/D/e
84
85 $ cd ..
86
76 87 issue 3340: mq does not handle case changes correctly
77 88
78 89 in addition to reported case, 'hg qrefresh' is also tested against
79 90 case changes.
80 91
81 92 $ echo "[extensions]" >> $HGRCPATH
82 93 $ echo "mq=" >> $HGRCPATH
83 94
84 95 $ hg init issue3340
85 96 $ cd issue3340
86 97
87 98 $ echo a > mIxEdCaSe
88 99 $ hg add mIxEdCaSe
89 100 $ hg commit -m '#0'
90 101 $ hg rename mIxEdCaSe tmp
91 102 $ hg rename tmp MiXeDcAsE
92 103 $ hg status -A
93 104 A MiXeDcAsE
94 105 mIxEdCaSe
95 106 R mIxEdCaSe
96 107 $ hg qnew changecase
97 108 $ hg status -A
98 109 C MiXeDcAsE
99 110
100 111 $ hg qpop -a
101 112 popping changecase
102 113 patch queue now empty
103 114 $ hg qnew refresh-casechange
104 115 $ hg status -A
105 116 C mIxEdCaSe
106 117 $ hg rename mIxEdCaSe tmp
107 118 $ hg rename tmp MiXeDcAsE
108 119 $ hg status -A
109 120 A MiXeDcAsE
110 121 mIxEdCaSe
111 122 R mIxEdCaSe
112 123 $ hg qrefresh
113 124 $ hg status -A
114 125 C MiXeDcAsE
115 126
116 127 $ cd ..
@@ -1,52 +1,53
1 1 import os
2 2 from mercurial import hg, ui
3 3 from mercurial.scmutil import walkrepos
4 from mercurial.util import checklink
4 5 from os import mkdir, chdir
5 6 from os.path import join as pjoin
6 7
7 8 u = ui.ui()
8 sym = getattr(os, 'symlink', False) and getattr(os.path, 'samestat', False)
9 sym = checklink('.')
9 10
10 11 hg.repository(u, 'top1', create=1)
11 12 mkdir('subdir')
12 13 chdir('subdir')
13 14 hg.repository(u, 'sub1', create=1)
14 15 mkdir('subsubdir')
15 16 chdir('subsubdir')
16 17 hg.repository(u, 'subsub1', create=1)
17 18 chdir(os.path.pardir)
18 19 if sym:
19 20 os.symlink(os.path.pardir, 'circle')
20 21 os.symlink(pjoin('subsubdir', 'subsub1'), 'subsub1')
21 22
22 23 def runtest():
23 24 reposet = frozenset(walkrepos('.', followsym=True))
24 25 if sym and (len(reposet) != 3):
25 26 print "reposet = %r" % (reposet,)
26 27 print "Found %d repositories when I should have found 3" % (len(reposet),)
27 28 if (not sym) and (len(reposet) != 2):
28 29 print "reposet = %r" % (reposet,)
29 30 print "Found %d repositories when I should have found 2" % (len(reposet),)
30 31 sub1set = frozenset((pjoin('.', 'sub1'),
31 32 pjoin('.', 'circle', 'subdir', 'sub1')))
32 33 if len(sub1set & reposet) != 1:
33 34 print "sub1set = %r" % (sub1set,)
34 35 print "reposet = %r" % (reposet,)
35 36 print "sub1set and reposet should have exactly one path in common."
36 37 sub2set = frozenset((pjoin('.', 'subsub1'),
37 38 pjoin('.', 'subsubdir', 'subsub1')))
38 39 if len(sub2set & reposet) != 1:
39 40 print "sub2set = %r" % (sub2set,)
40 41 print "reposet = %r" % (reposet,)
41 42 print "sub1set and reposet should have exactly one path in common."
42 43 sub3 = pjoin('.', 'circle', 'top1')
43 44 if sym and not (sub3 in reposet):
44 45 print "reposet = %r" % (reposet,)
45 46 print "Symbolic links are supported and %s is not in reposet" % (sub3,)
46 47
47 48 runtest()
48 49 if sym:
49 50 # Simulate not having symlinks.
50 51 del os.path.samestat
51 52 sym = False
52 53 runtest()
General Comments 0
You need to be logged in to leave comments. Login now