##// END OF EJS Templates
util: make atomictempfile avoid ambiguity of file stat if needed...
FUJIWARA Katsunori -
r29201:a109bf7e default
parent child Browse files
Show More
@@ -1,2810 +1,2821 b''
1 1 # util.py - Mercurial utility functions and platform specific implementations
2 2 #
3 3 # Copyright 2005 K. Thananchayan <thananck@yahoo.com>
4 4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
5 5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
6 6 #
7 7 # This software may be used and distributed according to the terms of the
8 8 # GNU General Public License version 2 or any later version.
9 9
10 10 """Mercurial utility functions and platform specific implementations.
11 11
12 12 This contains helper routines that are independent of the SCM core and
13 13 hide platform-specific details from the core.
14 14 """
15 15
16 16 from __future__ import absolute_import
17 17
18 18 import bz2
19 19 import calendar
20 20 import collections
21 21 import datetime
22 22 import errno
23 23 import gc
24 24 import hashlib
25 25 import imp
26 26 import os
27 27 import re as remod
28 28 import shutil
29 29 import signal
30 30 import socket
31 31 import subprocess
32 32 import sys
33 33 import tempfile
34 34 import textwrap
35 35 import time
36 36 import traceback
37 37 import zlib
38 38
39 39 from . import (
40 40 encoding,
41 41 error,
42 42 i18n,
43 43 osutil,
44 44 parsers,
45 45 pycompat,
46 46 )
47 47
48 48 for attr in (
49 49 'empty',
50 50 'queue',
51 51 'urlerr',
52 52 # we do import urlreq, but we do it outside the loop
53 53 #'urlreq',
54 54 'stringio',
55 55 ):
56 56 globals()[attr] = getattr(pycompat, attr)
57 57
58 58 # This line is to make pyflakes happy:
59 59 urlreq = pycompat.urlreq
60 60
61 61 if os.name == 'nt':
62 62 from . import windows as platform
63 63 else:
64 64 from . import posix as platform
65 65
66 66 md5 = hashlib.md5
67 67 sha1 = hashlib.sha1
68 68 sha512 = hashlib.sha512
69 69 _ = i18n._
70 70
71 71 cachestat = platform.cachestat
72 72 checkexec = platform.checkexec
73 73 checklink = platform.checklink
74 74 copymode = platform.copymode
75 75 executablepath = platform.executablepath
76 76 expandglobs = platform.expandglobs
77 77 explainexit = platform.explainexit
78 78 findexe = platform.findexe
79 79 gethgcmd = platform.gethgcmd
80 80 getuser = platform.getuser
81 81 getpid = os.getpid
82 82 groupmembers = platform.groupmembers
83 83 groupname = platform.groupname
84 84 hidewindow = platform.hidewindow
85 85 isexec = platform.isexec
86 86 isowner = platform.isowner
87 87 localpath = platform.localpath
88 88 lookupreg = platform.lookupreg
89 89 makedir = platform.makedir
90 90 nlinks = platform.nlinks
91 91 normpath = platform.normpath
92 92 normcase = platform.normcase
93 93 normcasespec = platform.normcasespec
94 94 normcasefallback = platform.normcasefallback
95 95 openhardlinks = platform.openhardlinks
96 96 oslink = platform.oslink
97 97 parsepatchoutput = platform.parsepatchoutput
98 98 pconvert = platform.pconvert
99 99 poll = platform.poll
100 100 popen = platform.popen
101 101 posixfile = platform.posixfile
102 102 quotecommand = platform.quotecommand
103 103 readpipe = platform.readpipe
104 104 rename = platform.rename
105 105 removedirs = platform.removedirs
106 106 samedevice = platform.samedevice
107 107 samefile = platform.samefile
108 108 samestat = platform.samestat
109 109 setbinary = platform.setbinary
110 110 setflags = platform.setflags
111 111 setsignalhandler = platform.setsignalhandler
112 112 shellquote = platform.shellquote
113 113 spawndetached = platform.spawndetached
114 114 split = platform.split
115 115 sshargs = platform.sshargs
116 116 statfiles = getattr(osutil, 'statfiles', platform.statfiles)
117 117 statisexec = platform.statisexec
118 118 statislink = platform.statislink
119 119 termwidth = platform.termwidth
120 120 testpid = platform.testpid
121 121 umask = platform.umask
122 122 unlink = platform.unlink
123 123 unlinkpath = platform.unlinkpath
124 124 username = platform.username
125 125
126 126 # Python compatibility
127 127
128 128 _notset = object()
129 129
130 130 # disable Python's problematic floating point timestamps (issue4836)
131 131 # (Python hypocritically says you shouldn't change this behavior in
132 132 # libraries, and sure enough Mercurial is not a library.)
133 133 os.stat_float_times(False)
134 134
135 135 def safehasattr(thing, attr):
136 136 return getattr(thing, attr, _notset) is not _notset
137 137
138 138 DIGESTS = {
139 139 'md5': md5,
140 140 'sha1': sha1,
141 141 'sha512': sha512,
142 142 }
143 143 # List of digest types from strongest to weakest
144 144 DIGESTS_BY_STRENGTH = ['sha512', 'sha1', 'md5']
145 145
146 146 for k in DIGESTS_BY_STRENGTH:
147 147 assert k in DIGESTS
148 148
149 149 class digester(object):
150 150 """helper to compute digests.
151 151
152 152 This helper can be used to compute one or more digests given their name.
153 153
154 154 >>> d = digester(['md5', 'sha1'])
155 155 >>> d.update('foo')
156 156 >>> [k for k in sorted(d)]
157 157 ['md5', 'sha1']
158 158 >>> d['md5']
159 159 'acbd18db4cc2f85cedef654fccc4a4d8'
160 160 >>> d['sha1']
161 161 '0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33'
162 162 >>> digester.preferred(['md5', 'sha1'])
163 163 'sha1'
164 164 """
165 165
166 166 def __init__(self, digests, s=''):
167 167 self._hashes = {}
168 168 for k in digests:
169 169 if k not in DIGESTS:
170 170 raise Abort(_('unknown digest type: %s') % k)
171 171 self._hashes[k] = DIGESTS[k]()
172 172 if s:
173 173 self.update(s)
174 174
175 175 def update(self, data):
176 176 for h in self._hashes.values():
177 177 h.update(data)
178 178
179 179 def __getitem__(self, key):
180 180 if key not in DIGESTS:
181 181 raise Abort(_('unknown digest type: %s') % k)
182 182 return self._hashes[key].hexdigest()
183 183
184 184 def __iter__(self):
185 185 return iter(self._hashes)
186 186
187 187 @staticmethod
188 188 def preferred(supported):
189 189 """returns the strongest digest type in both supported and DIGESTS."""
190 190
191 191 for k in DIGESTS_BY_STRENGTH:
192 192 if k in supported:
193 193 return k
194 194 return None
195 195
196 196 class digestchecker(object):
197 197 """file handle wrapper that additionally checks content against a given
198 198 size and digests.
199 199
200 200 d = digestchecker(fh, size, {'md5': '...'})
201 201
202 202 When multiple digests are given, all of them are validated.
203 203 """
204 204
205 205 def __init__(self, fh, size, digests):
206 206 self._fh = fh
207 207 self._size = size
208 208 self._got = 0
209 209 self._digests = dict(digests)
210 210 self._digester = digester(self._digests.keys())
211 211
212 212 def read(self, length=-1):
213 213 content = self._fh.read(length)
214 214 self._digester.update(content)
215 215 self._got += len(content)
216 216 return content
217 217
218 218 def validate(self):
219 219 if self._size != self._got:
220 220 raise Abort(_('size mismatch: expected %d, got %d') %
221 221 (self._size, self._got))
222 222 for k, v in self._digests.items():
223 223 if v != self._digester[k]:
224 224 # i18n: first parameter is a digest name
225 225 raise Abort(_('%s mismatch: expected %s, got %s') %
226 226 (k, v, self._digester[k]))
227 227
228 228 try:
229 229 buffer = buffer
230 230 except NameError:
231 231 if sys.version_info[0] < 3:
232 232 def buffer(sliceable, offset=0):
233 233 return sliceable[offset:]
234 234 else:
235 235 def buffer(sliceable, offset=0):
236 236 return memoryview(sliceable)[offset:]
237 237
238 238 closefds = os.name == 'posix'
239 239
240 240 _chunksize = 4096
241 241
242 242 class bufferedinputpipe(object):
243 243 """a manually buffered input pipe
244 244
245 245 Python will not let us use buffered IO and lazy reading with 'polling' at
246 246 the same time. We cannot probe the buffer state and select will not detect
247 247 that data are ready to read if they are already buffered.
248 248
249 249 This class let us work around that by implementing its own buffering
250 250 (allowing efficient readline) while offering a way to know if the buffer is
251 251 empty from the output (allowing collaboration of the buffer with polling).
252 252
253 253 This class lives in the 'util' module because it makes use of the 'os'
254 254 module from the python stdlib.
255 255 """
256 256
257 257 def __init__(self, input):
258 258 self._input = input
259 259 self._buffer = []
260 260 self._eof = False
261 261 self._lenbuf = 0
262 262
263 263 @property
264 264 def hasbuffer(self):
265 265 """True is any data is currently buffered
266 266
267 267 This will be used externally a pre-step for polling IO. If there is
268 268 already data then no polling should be set in place."""
269 269 return bool(self._buffer)
270 270
271 271 @property
272 272 def closed(self):
273 273 return self._input.closed
274 274
275 275 def fileno(self):
276 276 return self._input.fileno()
277 277
278 278 def close(self):
279 279 return self._input.close()
280 280
281 281 def read(self, size):
282 282 while (not self._eof) and (self._lenbuf < size):
283 283 self._fillbuffer()
284 284 return self._frombuffer(size)
285 285
286 286 def readline(self, *args, **kwargs):
287 287 if 1 < len(self._buffer):
288 288 # this should not happen because both read and readline end with a
289 289 # _frombuffer call that collapse it.
290 290 self._buffer = [''.join(self._buffer)]
291 291 self._lenbuf = len(self._buffer[0])
292 292 lfi = -1
293 293 if self._buffer:
294 294 lfi = self._buffer[-1].find('\n')
295 295 while (not self._eof) and lfi < 0:
296 296 self._fillbuffer()
297 297 if self._buffer:
298 298 lfi = self._buffer[-1].find('\n')
299 299 size = lfi + 1
300 300 if lfi < 0: # end of file
301 301 size = self._lenbuf
302 302 elif 1 < len(self._buffer):
303 303 # we need to take previous chunks into account
304 304 size += self._lenbuf - len(self._buffer[-1])
305 305 return self._frombuffer(size)
306 306
307 307 def _frombuffer(self, size):
308 308 """return at most 'size' data from the buffer
309 309
310 310 The data are removed from the buffer."""
311 311 if size == 0 or not self._buffer:
312 312 return ''
313 313 buf = self._buffer[0]
314 314 if 1 < len(self._buffer):
315 315 buf = ''.join(self._buffer)
316 316
317 317 data = buf[:size]
318 318 buf = buf[len(data):]
319 319 if buf:
320 320 self._buffer = [buf]
321 321 self._lenbuf = len(buf)
322 322 else:
323 323 self._buffer = []
324 324 self._lenbuf = 0
325 325 return data
326 326
327 327 def _fillbuffer(self):
328 328 """read data to the buffer"""
329 329 data = os.read(self._input.fileno(), _chunksize)
330 330 if not data:
331 331 self._eof = True
332 332 else:
333 333 self._lenbuf += len(data)
334 334 self._buffer.append(data)
335 335
336 336 def popen2(cmd, env=None, newlines=False):
337 337 # Setting bufsize to -1 lets the system decide the buffer size.
338 338 # The default for bufsize is 0, meaning unbuffered. This leads to
339 339 # poor performance on Mac OS X: http://bugs.python.org/issue4194
340 340 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
341 341 close_fds=closefds,
342 342 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
343 343 universal_newlines=newlines,
344 344 env=env)
345 345 return p.stdin, p.stdout
346 346
347 347 def popen3(cmd, env=None, newlines=False):
348 348 stdin, stdout, stderr, p = popen4(cmd, env, newlines)
349 349 return stdin, stdout, stderr
350 350
351 351 def popen4(cmd, env=None, newlines=False, bufsize=-1):
352 352 p = subprocess.Popen(cmd, shell=True, bufsize=bufsize,
353 353 close_fds=closefds,
354 354 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
355 355 stderr=subprocess.PIPE,
356 356 universal_newlines=newlines,
357 357 env=env)
358 358 return p.stdin, p.stdout, p.stderr, p
359 359
360 360 def version():
361 361 """Return version information if available."""
362 362 try:
363 363 from . import __version__
364 364 return __version__.version
365 365 except ImportError:
366 366 return 'unknown'
367 367
368 368 def versiontuple(v=None, n=4):
369 369 """Parses a Mercurial version string into an N-tuple.
370 370
371 371 The version string to be parsed is specified with the ``v`` argument.
372 372 If it isn't defined, the current Mercurial version string will be parsed.
373 373
374 374 ``n`` can be 2, 3, or 4. Here is how some version strings map to
375 375 returned values:
376 376
377 377 >>> v = '3.6.1+190-df9b73d2d444'
378 378 >>> versiontuple(v, 2)
379 379 (3, 6)
380 380 >>> versiontuple(v, 3)
381 381 (3, 6, 1)
382 382 >>> versiontuple(v, 4)
383 383 (3, 6, 1, '190-df9b73d2d444')
384 384
385 385 >>> versiontuple('3.6.1+190-df9b73d2d444+20151118')
386 386 (3, 6, 1, '190-df9b73d2d444+20151118')
387 387
388 388 >>> v = '3.6'
389 389 >>> versiontuple(v, 2)
390 390 (3, 6)
391 391 >>> versiontuple(v, 3)
392 392 (3, 6, None)
393 393 >>> versiontuple(v, 4)
394 394 (3, 6, None, None)
395 395 """
396 396 if not v:
397 397 v = version()
398 398 parts = v.split('+', 1)
399 399 if len(parts) == 1:
400 400 vparts, extra = parts[0], None
401 401 else:
402 402 vparts, extra = parts
403 403
404 404 vints = []
405 405 for i in vparts.split('.'):
406 406 try:
407 407 vints.append(int(i))
408 408 except ValueError:
409 409 break
410 410 # (3, 6) -> (3, 6, None)
411 411 while len(vints) < 3:
412 412 vints.append(None)
413 413
414 414 if n == 2:
415 415 return (vints[0], vints[1])
416 416 if n == 3:
417 417 return (vints[0], vints[1], vints[2])
418 418 if n == 4:
419 419 return (vints[0], vints[1], vints[2], extra)
420 420
421 421 # used by parsedate
422 422 defaultdateformats = (
423 423 '%Y-%m-%d %H:%M:%S',
424 424 '%Y-%m-%d %I:%M:%S%p',
425 425 '%Y-%m-%d %H:%M',
426 426 '%Y-%m-%d %I:%M%p',
427 427 '%Y-%m-%d',
428 428 '%m-%d',
429 429 '%m/%d',
430 430 '%m/%d/%y',
431 431 '%m/%d/%Y',
432 432 '%a %b %d %H:%M:%S %Y',
433 433 '%a %b %d %I:%M:%S%p %Y',
434 434 '%a, %d %b %Y %H:%M:%S', # GNU coreutils "/bin/date --rfc-2822"
435 435 '%b %d %H:%M:%S %Y',
436 436 '%b %d %I:%M:%S%p %Y',
437 437 '%b %d %H:%M:%S',
438 438 '%b %d %I:%M:%S%p',
439 439 '%b %d %H:%M',
440 440 '%b %d %I:%M%p',
441 441 '%b %d %Y',
442 442 '%b %d',
443 443 '%H:%M:%S',
444 444 '%I:%M:%S%p',
445 445 '%H:%M',
446 446 '%I:%M%p',
447 447 )
448 448
449 449 extendeddateformats = defaultdateformats + (
450 450 "%Y",
451 451 "%Y-%m",
452 452 "%b",
453 453 "%b %Y",
454 454 )
455 455
456 456 def cachefunc(func):
457 457 '''cache the result of function calls'''
458 458 # XXX doesn't handle keywords args
459 459 if func.__code__.co_argcount == 0:
460 460 cache = []
461 461 def f():
462 462 if len(cache) == 0:
463 463 cache.append(func())
464 464 return cache[0]
465 465 return f
466 466 cache = {}
467 467 if func.__code__.co_argcount == 1:
468 468 # we gain a small amount of time because
469 469 # we don't need to pack/unpack the list
470 470 def f(arg):
471 471 if arg not in cache:
472 472 cache[arg] = func(arg)
473 473 return cache[arg]
474 474 else:
475 475 def f(*args):
476 476 if args not in cache:
477 477 cache[args] = func(*args)
478 478 return cache[args]
479 479
480 480 return f
481 481
482 482 class sortdict(dict):
483 483 '''a simple sorted dictionary'''
484 484 def __init__(self, data=None):
485 485 self._list = []
486 486 if data:
487 487 self.update(data)
488 488 def copy(self):
489 489 return sortdict(self)
490 490 def __setitem__(self, key, val):
491 491 if key in self:
492 492 self._list.remove(key)
493 493 self._list.append(key)
494 494 dict.__setitem__(self, key, val)
495 495 def __iter__(self):
496 496 return self._list.__iter__()
497 497 def update(self, src):
498 498 if isinstance(src, dict):
499 499 src = src.iteritems()
500 500 for k, v in src:
501 501 self[k] = v
502 502 def clear(self):
503 503 dict.clear(self)
504 504 self._list = []
505 505 def items(self):
506 506 return [(k, self[k]) for k in self._list]
507 507 def __delitem__(self, key):
508 508 dict.__delitem__(self, key)
509 509 self._list.remove(key)
510 510 def pop(self, key, *args, **kwargs):
511 511 dict.pop(self, key, *args, **kwargs)
512 512 try:
513 513 self._list.remove(key)
514 514 except ValueError:
515 515 pass
516 516 def keys(self):
517 517 return self._list
518 518 def iterkeys(self):
519 519 return self._list.__iter__()
520 520 def iteritems(self):
521 521 for k in self._list:
522 522 yield k, self[k]
523 523 def insert(self, index, key, val):
524 524 self._list.insert(index, key)
525 525 dict.__setitem__(self, key, val)
526 526
527 527 class _lrucachenode(object):
528 528 """A node in a doubly linked list.
529 529
530 530 Holds a reference to nodes on either side as well as a key-value
531 531 pair for the dictionary entry.
532 532 """
533 533 __slots__ = ('next', 'prev', 'key', 'value')
534 534
535 535 def __init__(self):
536 536 self.next = None
537 537 self.prev = None
538 538
539 539 self.key = _notset
540 540 self.value = None
541 541
542 542 def markempty(self):
543 543 """Mark the node as emptied."""
544 544 self.key = _notset
545 545
546 546 class lrucachedict(object):
547 547 """Dict that caches most recent accesses and sets.
548 548
549 549 The dict consists of an actual backing dict - indexed by original
550 550 key - and a doubly linked circular list defining the order of entries in
551 551 the cache.
552 552
553 553 The head node is the newest entry in the cache. If the cache is full,
554 554 we recycle head.prev and make it the new head. Cache accesses result in
555 555 the node being moved to before the existing head and being marked as the
556 556 new head node.
557 557 """
558 558 def __init__(self, max):
559 559 self._cache = {}
560 560
561 561 self._head = head = _lrucachenode()
562 562 head.prev = head
563 563 head.next = head
564 564 self._size = 1
565 565 self._capacity = max
566 566
567 567 def __len__(self):
568 568 return len(self._cache)
569 569
570 570 def __contains__(self, k):
571 571 return k in self._cache
572 572
573 573 def __iter__(self):
574 574 # We don't have to iterate in cache order, but why not.
575 575 n = self._head
576 576 for i in range(len(self._cache)):
577 577 yield n.key
578 578 n = n.next
579 579
580 580 def __getitem__(self, k):
581 581 node = self._cache[k]
582 582 self._movetohead(node)
583 583 return node.value
584 584
585 585 def __setitem__(self, k, v):
586 586 node = self._cache.get(k)
587 587 # Replace existing value and mark as newest.
588 588 if node is not None:
589 589 node.value = v
590 590 self._movetohead(node)
591 591 return
592 592
593 593 if self._size < self._capacity:
594 594 node = self._addcapacity()
595 595 else:
596 596 # Grab the last/oldest item.
597 597 node = self._head.prev
598 598
599 599 # At capacity. Kill the old entry.
600 600 if node.key is not _notset:
601 601 del self._cache[node.key]
602 602
603 603 node.key = k
604 604 node.value = v
605 605 self._cache[k] = node
606 606 # And mark it as newest entry. No need to adjust order since it
607 607 # is already self._head.prev.
608 608 self._head = node
609 609
610 610 def __delitem__(self, k):
611 611 node = self._cache.pop(k)
612 612 node.markempty()
613 613
614 614 # Temporarily mark as newest item before re-adjusting head to make
615 615 # this node the oldest item.
616 616 self._movetohead(node)
617 617 self._head = node.next
618 618
619 619 # Additional dict methods.
620 620
621 621 def get(self, k, default=None):
622 622 try:
623 623 return self._cache[k]
624 624 except KeyError:
625 625 return default
626 626
627 627 def clear(self):
628 628 n = self._head
629 629 while n.key is not _notset:
630 630 n.markempty()
631 631 n = n.next
632 632
633 633 self._cache.clear()
634 634
635 635 def copy(self):
636 636 result = lrucachedict(self._capacity)
637 637 n = self._head.prev
638 638 # Iterate in oldest-to-newest order, so the copy has the right ordering
639 639 for i in range(len(self._cache)):
640 640 result[n.key] = n.value
641 641 n = n.prev
642 642 return result
643 643
644 644 def _movetohead(self, node):
645 645 """Mark a node as the newest, making it the new head.
646 646
647 647 When a node is accessed, it becomes the freshest entry in the LRU
648 648 list, which is denoted by self._head.
649 649
650 650 Visually, let's make ``N`` the new head node (* denotes head):
651 651
652 652 previous/oldest <-> head <-> next/next newest
653 653
654 654 ----<->--- A* ---<->-----
655 655 | |
656 656 E <-> D <-> N <-> C <-> B
657 657
658 658 To:
659 659
660 660 ----<->--- N* ---<->-----
661 661 | |
662 662 E <-> D <-> C <-> B <-> A
663 663
664 664 This requires the following moves:
665 665
666 666 C.next = D (node.prev.next = node.next)
667 667 D.prev = C (node.next.prev = node.prev)
668 668 E.next = N (head.prev.next = node)
669 669 N.prev = E (node.prev = head.prev)
670 670 N.next = A (node.next = head)
671 671 A.prev = N (head.prev = node)
672 672 """
673 673 head = self._head
674 674 # C.next = D
675 675 node.prev.next = node.next
676 676 # D.prev = C
677 677 node.next.prev = node.prev
678 678 # N.prev = E
679 679 node.prev = head.prev
680 680 # N.next = A
681 681 # It is tempting to do just "head" here, however if node is
682 682 # adjacent to head, this will do bad things.
683 683 node.next = head.prev.next
684 684 # E.next = N
685 685 node.next.prev = node
686 686 # A.prev = N
687 687 node.prev.next = node
688 688
689 689 self._head = node
690 690
691 691 def _addcapacity(self):
692 692 """Add a node to the circular linked list.
693 693
694 694 The new node is inserted before the head node.
695 695 """
696 696 head = self._head
697 697 node = _lrucachenode()
698 698 head.prev.next = node
699 699 node.prev = head.prev
700 700 node.next = head
701 701 head.prev = node
702 702 self._size += 1
703 703 return node
704 704
705 705 def lrucachefunc(func):
706 706 '''cache most recent results of function calls'''
707 707 cache = {}
708 708 order = collections.deque()
709 709 if func.__code__.co_argcount == 1:
710 710 def f(arg):
711 711 if arg not in cache:
712 712 if len(cache) > 20:
713 713 del cache[order.popleft()]
714 714 cache[arg] = func(arg)
715 715 else:
716 716 order.remove(arg)
717 717 order.append(arg)
718 718 return cache[arg]
719 719 else:
720 720 def f(*args):
721 721 if args not in cache:
722 722 if len(cache) > 20:
723 723 del cache[order.popleft()]
724 724 cache[args] = func(*args)
725 725 else:
726 726 order.remove(args)
727 727 order.append(args)
728 728 return cache[args]
729 729
730 730 return f
731 731
732 732 class propertycache(object):
733 733 def __init__(self, func):
734 734 self.func = func
735 735 self.name = func.__name__
736 736 def __get__(self, obj, type=None):
737 737 result = self.func(obj)
738 738 self.cachevalue(obj, result)
739 739 return result
740 740
741 741 def cachevalue(self, obj, value):
742 742 # __dict__ assignment required to bypass __setattr__ (eg: repoview)
743 743 obj.__dict__[self.name] = value
744 744
745 745 def pipefilter(s, cmd):
746 746 '''filter string S through command CMD, returning its output'''
747 747 p = subprocess.Popen(cmd, shell=True, close_fds=closefds,
748 748 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
749 749 pout, perr = p.communicate(s)
750 750 return pout
751 751
752 752 def tempfilter(s, cmd):
753 753 '''filter string S through a pair of temporary files with CMD.
754 754 CMD is used as a template to create the real command to be run,
755 755 with the strings INFILE and OUTFILE replaced by the real names of
756 756 the temporary files generated.'''
757 757 inname, outname = None, None
758 758 try:
759 759 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
760 760 fp = os.fdopen(infd, 'wb')
761 761 fp.write(s)
762 762 fp.close()
763 763 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
764 764 os.close(outfd)
765 765 cmd = cmd.replace('INFILE', inname)
766 766 cmd = cmd.replace('OUTFILE', outname)
767 767 code = os.system(cmd)
768 768 if sys.platform == 'OpenVMS' and code & 1:
769 769 code = 0
770 770 if code:
771 771 raise Abort(_("command '%s' failed: %s") %
772 772 (cmd, explainexit(code)))
773 773 return readfile(outname)
774 774 finally:
775 775 try:
776 776 if inname:
777 777 os.unlink(inname)
778 778 except OSError:
779 779 pass
780 780 try:
781 781 if outname:
782 782 os.unlink(outname)
783 783 except OSError:
784 784 pass
785 785
786 786 filtertable = {
787 787 'tempfile:': tempfilter,
788 788 'pipe:': pipefilter,
789 789 }
790 790
791 791 def filter(s, cmd):
792 792 "filter a string through a command that transforms its input to its output"
793 793 for name, fn in filtertable.iteritems():
794 794 if cmd.startswith(name):
795 795 return fn(s, cmd[len(name):].lstrip())
796 796 return pipefilter(s, cmd)
797 797
798 798 def binary(s):
799 799 """return true if a string is binary data"""
800 800 return bool(s and '\0' in s)
801 801
802 802 def increasingchunks(source, min=1024, max=65536):
803 803 '''return no less than min bytes per chunk while data remains,
804 804 doubling min after each chunk until it reaches max'''
805 805 def log2(x):
806 806 if not x:
807 807 return 0
808 808 i = 0
809 809 while x:
810 810 x >>= 1
811 811 i += 1
812 812 return i - 1
813 813
814 814 buf = []
815 815 blen = 0
816 816 for chunk in source:
817 817 buf.append(chunk)
818 818 blen += len(chunk)
819 819 if blen >= min:
820 820 if min < max:
821 821 min = min << 1
822 822 nmin = 1 << log2(blen)
823 823 if nmin > min:
824 824 min = nmin
825 825 if min > max:
826 826 min = max
827 827 yield ''.join(buf)
828 828 blen = 0
829 829 buf = []
830 830 if buf:
831 831 yield ''.join(buf)
832 832
833 833 Abort = error.Abort
834 834
835 835 def always(fn):
836 836 return True
837 837
838 838 def never(fn):
839 839 return False
840 840
841 841 def nogc(func):
842 842 """disable garbage collector
843 843
844 844 Python's garbage collector triggers a GC each time a certain number of
845 845 container objects (the number being defined by gc.get_threshold()) are
846 846 allocated even when marked not to be tracked by the collector. Tracking has
847 847 no effect on when GCs are triggered, only on what objects the GC looks
848 848 into. As a workaround, disable GC while building complex (huge)
849 849 containers.
850 850
851 851 This garbage collector issue have been fixed in 2.7.
852 852 """
853 853 def wrapper(*args, **kwargs):
854 854 gcenabled = gc.isenabled()
855 855 gc.disable()
856 856 try:
857 857 return func(*args, **kwargs)
858 858 finally:
859 859 if gcenabled:
860 860 gc.enable()
861 861 return wrapper
862 862
863 863 def pathto(root, n1, n2):
864 864 '''return the relative path from one place to another.
865 865 root should use os.sep to separate directories
866 866 n1 should use os.sep to separate directories
867 867 n2 should use "/" to separate directories
868 868 returns an os.sep-separated path.
869 869
870 870 If n1 is a relative path, it's assumed it's
871 871 relative to root.
872 872 n2 should always be relative to root.
873 873 '''
874 874 if not n1:
875 875 return localpath(n2)
876 876 if os.path.isabs(n1):
877 877 if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
878 878 return os.path.join(root, localpath(n2))
879 879 n2 = '/'.join((pconvert(root), n2))
880 880 a, b = splitpath(n1), n2.split('/')
881 881 a.reverse()
882 882 b.reverse()
883 883 while a and b and a[-1] == b[-1]:
884 884 a.pop()
885 885 b.pop()
886 886 b.reverse()
887 887 return os.sep.join((['..'] * len(a)) + b) or '.'
888 888
889 889 def mainfrozen():
890 890 """return True if we are a frozen executable.
891 891
892 892 The code supports py2exe (most common, Windows only) and tools/freeze
893 893 (portable, not much used).
894 894 """
895 895 return (safehasattr(sys, "frozen") or # new py2exe
896 896 safehasattr(sys, "importers") or # old py2exe
897 897 imp.is_frozen("__main__")) # tools/freeze
898 898
899 899 # the location of data files matching the source code
900 900 if mainfrozen() and getattr(sys, 'frozen', None) != 'macosx_app':
901 901 # executable version (py2exe) doesn't support __file__
902 902 datapath = os.path.dirname(sys.executable)
903 903 else:
904 904 datapath = os.path.dirname(__file__)
905 905
906 906 i18n.setdatapath(datapath)
907 907
908 908 _hgexecutable = None
909 909
910 910 def hgexecutable():
911 911 """return location of the 'hg' executable.
912 912
913 913 Defaults to $HG or 'hg' in the search path.
914 914 """
915 915 if _hgexecutable is None:
916 916 hg = os.environ.get('HG')
917 917 mainmod = sys.modules['__main__']
918 918 if hg:
919 919 _sethgexecutable(hg)
920 920 elif mainfrozen():
921 921 if getattr(sys, 'frozen', None) == 'macosx_app':
922 922 # Env variable set by py2app
923 923 _sethgexecutable(os.environ['EXECUTABLEPATH'])
924 924 else:
925 925 _sethgexecutable(sys.executable)
926 926 elif os.path.basename(getattr(mainmod, '__file__', '')) == 'hg':
927 927 _sethgexecutable(mainmod.__file__)
928 928 else:
929 929 exe = findexe('hg') or os.path.basename(sys.argv[0])
930 930 _sethgexecutable(exe)
931 931 return _hgexecutable
932 932
933 933 def _sethgexecutable(path):
934 934 """set location of the 'hg' executable"""
935 935 global _hgexecutable
936 936 _hgexecutable = path
937 937
938 938 def _isstdout(f):
939 939 fileno = getattr(f, 'fileno', None)
940 940 return fileno and fileno() == sys.__stdout__.fileno()
941 941
942 942 def system(cmd, environ=None, cwd=None, onerr=None, errprefix=None, out=None):
943 943 '''enhanced shell command execution.
944 944 run with environment maybe modified, maybe in different dir.
945 945
946 946 if command fails and onerr is None, return status, else raise onerr
947 947 object as exception.
948 948
949 949 if out is specified, it is assumed to be a file-like object that has a
950 950 write() method. stdout and stderr will be redirected to out.'''
951 951 if environ is None:
952 952 environ = {}
953 953 try:
954 954 sys.stdout.flush()
955 955 except Exception:
956 956 pass
957 957 def py2shell(val):
958 958 'convert python object into string that is useful to shell'
959 959 if val is None or val is False:
960 960 return '0'
961 961 if val is True:
962 962 return '1'
963 963 return str(val)
964 964 origcmd = cmd
965 965 cmd = quotecommand(cmd)
966 966 if sys.platform == 'plan9' and (sys.version_info[0] == 2
967 967 and sys.version_info[1] < 7):
968 968 # subprocess kludge to work around issues in half-baked Python
969 969 # ports, notably bichued/python:
970 970 if not cwd is None:
971 971 os.chdir(cwd)
972 972 rc = os.system(cmd)
973 973 else:
974 974 env = dict(os.environ)
975 975 env.update((k, py2shell(v)) for k, v in environ.iteritems())
976 976 env['HG'] = hgexecutable()
977 977 if out is None or _isstdout(out):
978 978 rc = subprocess.call(cmd, shell=True, close_fds=closefds,
979 979 env=env, cwd=cwd)
980 980 else:
981 981 proc = subprocess.Popen(cmd, shell=True, close_fds=closefds,
982 982 env=env, cwd=cwd, stdout=subprocess.PIPE,
983 983 stderr=subprocess.STDOUT)
984 984 while True:
985 985 line = proc.stdout.readline()
986 986 if not line:
987 987 break
988 988 out.write(line)
989 989 proc.wait()
990 990 rc = proc.returncode
991 991 if sys.platform == 'OpenVMS' and rc & 1:
992 992 rc = 0
993 993 if rc and onerr:
994 994 errmsg = '%s %s' % (os.path.basename(origcmd.split(None, 1)[0]),
995 995 explainexit(rc)[0])
996 996 if errprefix:
997 997 errmsg = '%s: %s' % (errprefix, errmsg)
998 998 raise onerr(errmsg)
999 999 return rc
1000 1000
1001 1001 def checksignature(func):
1002 1002 '''wrap a function with code to check for calling errors'''
1003 1003 def check(*args, **kwargs):
1004 1004 try:
1005 1005 return func(*args, **kwargs)
1006 1006 except TypeError:
1007 1007 if len(traceback.extract_tb(sys.exc_info()[2])) == 1:
1008 1008 raise error.SignatureError
1009 1009 raise
1010 1010
1011 1011 return check
1012 1012
1013 1013 def copyfile(src, dest, hardlink=False, copystat=False):
1014 1014 '''copy a file, preserving mode and optionally other stat info like
1015 1015 atime/mtime'''
1016 1016 if os.path.lexists(dest):
1017 1017 unlink(dest)
1018 1018 # hardlinks are problematic on CIFS, quietly ignore this flag
1019 1019 # until we find a way to work around it cleanly (issue4546)
1020 1020 if False and hardlink:
1021 1021 try:
1022 1022 oslink(src, dest)
1023 1023 return
1024 1024 except (IOError, OSError):
1025 1025 pass # fall back to normal copy
1026 1026 if os.path.islink(src):
1027 1027 os.symlink(os.readlink(src), dest)
1028 1028 # copytime is ignored for symlinks, but in general copytime isn't needed
1029 1029 # for them anyway
1030 1030 else:
1031 1031 try:
1032 1032 shutil.copyfile(src, dest)
1033 1033 if copystat:
1034 1034 # copystat also copies mode
1035 1035 shutil.copystat(src, dest)
1036 1036 else:
1037 1037 shutil.copymode(src, dest)
1038 1038 except shutil.Error as inst:
1039 1039 raise Abort(str(inst))
1040 1040
1041 1041 def copyfiles(src, dst, hardlink=None, progress=lambda t, pos: None):
1042 1042 """Copy a directory tree using hardlinks if possible."""
1043 1043 num = 0
1044 1044
1045 1045 if hardlink is None:
1046 1046 hardlink = (os.stat(src).st_dev ==
1047 1047 os.stat(os.path.dirname(dst)).st_dev)
1048 1048 if hardlink:
1049 1049 topic = _('linking')
1050 1050 else:
1051 1051 topic = _('copying')
1052 1052
1053 1053 if os.path.isdir(src):
1054 1054 os.mkdir(dst)
1055 1055 for name, kind in osutil.listdir(src):
1056 1056 srcname = os.path.join(src, name)
1057 1057 dstname = os.path.join(dst, name)
1058 1058 def nprog(t, pos):
1059 1059 if pos is not None:
1060 1060 return progress(t, pos + num)
1061 1061 hardlink, n = copyfiles(srcname, dstname, hardlink, progress=nprog)
1062 1062 num += n
1063 1063 else:
1064 1064 if hardlink:
1065 1065 try:
1066 1066 oslink(src, dst)
1067 1067 except (IOError, OSError):
1068 1068 hardlink = False
1069 1069 shutil.copy(src, dst)
1070 1070 else:
1071 1071 shutil.copy(src, dst)
1072 1072 num += 1
1073 1073 progress(topic, num)
1074 1074 progress(topic, None)
1075 1075
1076 1076 return hardlink, num
1077 1077
1078 1078 _winreservednames = '''con prn aux nul
1079 1079 com1 com2 com3 com4 com5 com6 com7 com8 com9
1080 1080 lpt1 lpt2 lpt3 lpt4 lpt5 lpt6 lpt7 lpt8 lpt9'''.split()
1081 1081 _winreservedchars = ':*?"<>|'
1082 1082 def checkwinfilename(path):
1083 1083 r'''Check that the base-relative path is a valid filename on Windows.
1084 1084 Returns None if the path is ok, or a UI string describing the problem.
1085 1085
1086 1086 >>> checkwinfilename("just/a/normal/path")
1087 1087 >>> checkwinfilename("foo/bar/con.xml")
1088 1088 "filename contains 'con', which is reserved on Windows"
1089 1089 >>> checkwinfilename("foo/con.xml/bar")
1090 1090 "filename contains 'con', which is reserved on Windows"
1091 1091 >>> checkwinfilename("foo/bar/xml.con")
1092 1092 >>> checkwinfilename("foo/bar/AUX/bla.txt")
1093 1093 "filename contains 'AUX', which is reserved on Windows"
1094 1094 >>> checkwinfilename("foo/bar/bla:.txt")
1095 1095 "filename contains ':', which is reserved on Windows"
1096 1096 >>> checkwinfilename("foo/bar/b\07la.txt")
1097 1097 "filename contains '\\x07', which is invalid on Windows"
1098 1098 >>> checkwinfilename("foo/bar/bla ")
1099 1099 "filename ends with ' ', which is not allowed on Windows"
1100 1100 >>> checkwinfilename("../bar")
1101 1101 >>> checkwinfilename("foo\\")
1102 1102 "filename ends with '\\', which is invalid on Windows"
1103 1103 >>> checkwinfilename("foo\\/bar")
1104 1104 "directory name ends with '\\', which is invalid on Windows"
1105 1105 '''
1106 1106 if path.endswith('\\'):
1107 1107 return _("filename ends with '\\', which is invalid on Windows")
1108 1108 if '\\/' in path:
1109 1109 return _("directory name ends with '\\', which is invalid on Windows")
1110 1110 for n in path.replace('\\', '/').split('/'):
1111 1111 if not n:
1112 1112 continue
1113 1113 for c in n:
1114 1114 if c in _winreservedchars:
1115 1115 return _("filename contains '%s', which is reserved "
1116 1116 "on Windows") % c
1117 1117 if ord(c) <= 31:
1118 1118 return _("filename contains %r, which is invalid "
1119 1119 "on Windows") % c
1120 1120 base = n.split('.')[0]
1121 1121 if base and base.lower() in _winreservednames:
1122 1122 return _("filename contains '%s', which is reserved "
1123 1123 "on Windows") % base
1124 1124 t = n[-1]
1125 1125 if t in '. ' and n not in '..':
1126 1126 return _("filename ends with '%s', which is not allowed "
1127 1127 "on Windows") % t
1128 1128
1129 1129 if os.name == 'nt':
1130 1130 checkosfilename = checkwinfilename
1131 1131 else:
1132 1132 checkosfilename = platform.checkosfilename
1133 1133
1134 1134 def makelock(info, pathname):
1135 1135 try:
1136 1136 return os.symlink(info, pathname)
1137 1137 except OSError as why:
1138 1138 if why.errno == errno.EEXIST:
1139 1139 raise
1140 1140 except AttributeError: # no symlink in os
1141 1141 pass
1142 1142
1143 1143 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
1144 1144 os.write(ld, info)
1145 1145 os.close(ld)
1146 1146
1147 1147 def readlock(pathname):
1148 1148 try:
1149 1149 return os.readlink(pathname)
1150 1150 except OSError as why:
1151 1151 if why.errno not in (errno.EINVAL, errno.ENOSYS):
1152 1152 raise
1153 1153 except AttributeError: # no symlink in os
1154 1154 pass
1155 1155 fp = posixfile(pathname)
1156 1156 r = fp.read()
1157 1157 fp.close()
1158 1158 return r
1159 1159
1160 1160 def fstat(fp):
1161 1161 '''stat file object that may not have fileno method.'''
1162 1162 try:
1163 1163 return os.fstat(fp.fileno())
1164 1164 except AttributeError:
1165 1165 return os.stat(fp.name)
1166 1166
1167 1167 # File system features
1168 1168
1169 1169 def checkcase(path):
1170 1170 """
1171 1171 Return true if the given path is on a case-sensitive filesystem
1172 1172
1173 1173 Requires a path (like /foo/.hg) ending with a foldable final
1174 1174 directory component.
1175 1175 """
1176 1176 s1 = os.lstat(path)
1177 1177 d, b = os.path.split(path)
1178 1178 b2 = b.upper()
1179 1179 if b == b2:
1180 1180 b2 = b.lower()
1181 1181 if b == b2:
1182 1182 return True # no evidence against case sensitivity
1183 1183 p2 = os.path.join(d, b2)
1184 1184 try:
1185 1185 s2 = os.lstat(p2)
1186 1186 if s2 == s1:
1187 1187 return False
1188 1188 return True
1189 1189 except OSError:
1190 1190 return True
1191 1191
1192 1192 try:
1193 1193 import re2
1194 1194 _re2 = None
1195 1195 except ImportError:
1196 1196 _re2 = False
1197 1197
1198 1198 class _re(object):
1199 1199 def _checkre2(self):
1200 1200 global _re2
1201 1201 try:
1202 1202 # check if match works, see issue3964
1203 1203 _re2 = bool(re2.match(r'\[([^\[]+)\]', '[ui]'))
1204 1204 except ImportError:
1205 1205 _re2 = False
1206 1206
1207 1207 def compile(self, pat, flags=0):
1208 1208 '''Compile a regular expression, using re2 if possible
1209 1209
1210 1210 For best performance, use only re2-compatible regexp features. The
1211 1211 only flags from the re module that are re2-compatible are
1212 1212 IGNORECASE and MULTILINE.'''
1213 1213 if _re2 is None:
1214 1214 self._checkre2()
1215 1215 if _re2 and (flags & ~(remod.IGNORECASE | remod.MULTILINE)) == 0:
1216 1216 if flags & remod.IGNORECASE:
1217 1217 pat = '(?i)' + pat
1218 1218 if flags & remod.MULTILINE:
1219 1219 pat = '(?m)' + pat
1220 1220 try:
1221 1221 return re2.compile(pat)
1222 1222 except re2.error:
1223 1223 pass
1224 1224 return remod.compile(pat, flags)
1225 1225
1226 1226 @propertycache
1227 1227 def escape(self):
1228 1228 '''Return the version of escape corresponding to self.compile.
1229 1229
1230 1230 This is imperfect because whether re2 or re is used for a particular
1231 1231 function depends on the flags, etc, but it's the best we can do.
1232 1232 '''
1233 1233 global _re2
1234 1234 if _re2 is None:
1235 1235 self._checkre2()
1236 1236 if _re2:
1237 1237 return re2.escape
1238 1238 else:
1239 1239 return remod.escape
1240 1240
1241 1241 re = _re()
1242 1242
1243 1243 _fspathcache = {}
1244 1244 def fspath(name, root):
1245 1245 '''Get name in the case stored in the filesystem
1246 1246
1247 1247 The name should be relative to root, and be normcase-ed for efficiency.
1248 1248
1249 1249 Note that this function is unnecessary, and should not be
1250 1250 called, for case-sensitive filesystems (simply because it's expensive).
1251 1251
1252 1252 The root should be normcase-ed, too.
1253 1253 '''
1254 1254 def _makefspathcacheentry(dir):
1255 1255 return dict((normcase(n), n) for n in os.listdir(dir))
1256 1256
1257 1257 seps = os.sep
1258 1258 if os.altsep:
1259 1259 seps = seps + os.altsep
1260 1260 # Protect backslashes. This gets silly very quickly.
1261 1261 seps.replace('\\','\\\\')
1262 1262 pattern = remod.compile(r'([^%s]+)|([%s]+)' % (seps, seps))
1263 1263 dir = os.path.normpath(root)
1264 1264 result = []
1265 1265 for part, sep in pattern.findall(name):
1266 1266 if sep:
1267 1267 result.append(sep)
1268 1268 continue
1269 1269
1270 1270 if dir not in _fspathcache:
1271 1271 _fspathcache[dir] = _makefspathcacheentry(dir)
1272 1272 contents = _fspathcache[dir]
1273 1273
1274 1274 found = contents.get(part)
1275 1275 if not found:
1276 1276 # retry "once per directory" per "dirstate.walk" which
1277 1277 # may take place for each patches of "hg qpush", for example
1278 1278 _fspathcache[dir] = contents = _makefspathcacheentry(dir)
1279 1279 found = contents.get(part)
1280 1280
1281 1281 result.append(found or part)
1282 1282 dir = os.path.join(dir, part)
1283 1283
1284 1284 return ''.join(result)
1285 1285
1286 1286 def checknlink(testfile):
1287 1287 '''check whether hardlink count reporting works properly'''
1288 1288
1289 1289 # testfile may be open, so we need a separate file for checking to
1290 1290 # work around issue2543 (or testfile may get lost on Samba shares)
1291 1291 f1 = testfile + ".hgtmp1"
1292 1292 if os.path.lexists(f1):
1293 1293 return False
1294 1294 try:
1295 1295 posixfile(f1, 'w').close()
1296 1296 except IOError:
1297 1297 return False
1298 1298
1299 1299 f2 = testfile + ".hgtmp2"
1300 1300 fd = None
1301 1301 try:
1302 1302 oslink(f1, f2)
1303 1303 # nlinks() may behave differently for files on Windows shares if
1304 1304 # the file is open.
1305 1305 fd = posixfile(f2)
1306 1306 return nlinks(f2) > 1
1307 1307 except OSError:
1308 1308 return False
1309 1309 finally:
1310 1310 if fd is not None:
1311 1311 fd.close()
1312 1312 for f in (f1, f2):
1313 1313 try:
1314 1314 os.unlink(f)
1315 1315 except OSError:
1316 1316 pass
1317 1317
1318 1318 def endswithsep(path):
1319 1319 '''Check path ends with os.sep or os.altsep.'''
1320 1320 return path.endswith(os.sep) or os.altsep and path.endswith(os.altsep)
1321 1321
1322 1322 def splitpath(path):
1323 1323 '''Split path by os.sep.
1324 1324 Note that this function does not use os.altsep because this is
1325 1325 an alternative of simple "xxx.split(os.sep)".
1326 1326 It is recommended to use os.path.normpath() before using this
1327 1327 function if need.'''
1328 1328 return path.split(os.sep)
1329 1329
1330 1330 def gui():
1331 1331 '''Are we running in a GUI?'''
1332 1332 if sys.platform == 'darwin':
1333 1333 if 'SSH_CONNECTION' in os.environ:
1334 1334 # handle SSH access to a box where the user is logged in
1335 1335 return False
1336 1336 elif getattr(osutil, 'isgui', None):
1337 1337 # check if a CoreGraphics session is available
1338 1338 return osutil.isgui()
1339 1339 else:
1340 1340 # pure build; use a safe default
1341 1341 return True
1342 1342 else:
1343 1343 return os.name == "nt" or os.environ.get("DISPLAY")
1344 1344
1345 1345 def mktempcopy(name, emptyok=False, createmode=None):
1346 1346 """Create a temporary file with the same contents from name
1347 1347
1348 1348 The permission bits are copied from the original file.
1349 1349
1350 1350 If the temporary file is going to be truncated immediately, you
1351 1351 can use emptyok=True as an optimization.
1352 1352
1353 1353 Returns the name of the temporary file.
1354 1354 """
1355 1355 d, fn = os.path.split(name)
1356 1356 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
1357 1357 os.close(fd)
1358 1358 # Temporary files are created with mode 0600, which is usually not
1359 1359 # what we want. If the original file already exists, just copy
1360 1360 # its mode. Otherwise, manually obey umask.
1361 1361 copymode(name, temp, createmode)
1362 1362 if emptyok:
1363 1363 return temp
1364 1364 try:
1365 1365 try:
1366 1366 ifp = posixfile(name, "rb")
1367 1367 except IOError as inst:
1368 1368 if inst.errno == errno.ENOENT:
1369 1369 return temp
1370 1370 if not getattr(inst, 'filename', None):
1371 1371 inst.filename = name
1372 1372 raise
1373 1373 ofp = posixfile(temp, "wb")
1374 1374 for chunk in filechunkiter(ifp):
1375 1375 ofp.write(chunk)
1376 1376 ifp.close()
1377 1377 ofp.close()
1378 1378 except: # re-raises
1379 1379 try: os.unlink(temp)
1380 1380 except OSError: pass
1381 1381 raise
1382 1382 return temp
1383 1383
1384 1384 class filestat(object):
1385 1385 """help to exactly detect change of a file
1386 1386
1387 1387 'stat' attribute is result of 'os.stat()' if specified 'path'
1388 1388 exists. Otherwise, it is None. This can avoid preparative
1389 1389 'exists()' examination on client side of this class.
1390 1390 """
1391 1391 def __init__(self, path):
1392 1392 try:
1393 1393 self.stat = os.stat(path)
1394 1394 except OSError as err:
1395 1395 if err.errno != errno.ENOENT:
1396 1396 raise
1397 1397 self.stat = None
1398 1398
1399 1399 __hash__ = object.__hash__
1400 1400
1401 1401 def __eq__(self, old):
1402 1402 try:
1403 1403 # if ambiguity between stat of new and old file is
1404 1404 # avoided, comparision of size, ctime and mtime is enough
1405 1405 # to exactly detect change of a file regardless of platform
1406 1406 return (self.stat.st_size == old.stat.st_size and
1407 1407 self.stat.st_ctime == old.stat.st_ctime and
1408 1408 self.stat.st_mtime == old.stat.st_mtime)
1409 1409 except AttributeError:
1410 1410 return False
1411 1411
1412 1412 def isambig(self, old):
1413 1413 """Examine whether new (= self) stat is ambiguous against old one
1414 1414
1415 1415 "S[N]" below means stat of a file at N-th change:
1416 1416
1417 1417 - S[n-1].ctime < S[n].ctime: can detect change of a file
1418 1418 - S[n-1].ctime == S[n].ctime
1419 1419 - S[n-1].ctime < S[n].mtime: means natural advancing (*1)
1420 1420 - S[n-1].ctime == S[n].mtime: is ambiguous (*2)
1421 1421 - S[n-1].ctime > S[n].mtime: never occurs naturally (don't care)
1422 1422 - S[n-1].ctime > S[n].ctime: never occurs naturally (don't care)
1423 1423
1424 1424 Case (*2) above means that a file was changed twice or more at
1425 1425 same time in sec (= S[n-1].ctime), and comparison of timestamp
1426 1426 is ambiguous.
1427 1427
1428 1428 Base idea to avoid such ambiguity is "advance mtime 1 sec, if
1429 1429 timestamp is ambiguous".
1430 1430
1431 1431 But advancing mtime only in case (*2) doesn't work as
1432 1432 expected, because naturally advanced S[n].mtime in case (*1)
1433 1433 might be equal to manually advanced S[n-1 or earlier].mtime.
1434 1434
1435 1435 Therefore, all "S[n-1].ctime == S[n].ctime" cases should be
1436 1436 treated as ambiguous regardless of mtime, to avoid overlooking
1437 1437 by confliction between such mtime.
1438 1438
1439 1439 Advancing mtime "if isambig(oldstat)" ensures "S[n-1].mtime !=
1440 1440 S[n].mtime", even if size of a file isn't changed.
1441 1441 """
1442 1442 try:
1443 1443 return (self.stat.st_ctime == old.stat.st_ctime)
1444 1444 except AttributeError:
1445 1445 return False
1446 1446
1447 1447 class atomictempfile(object):
1448 1448 '''writable file object that atomically updates a file
1449 1449
1450 1450 All writes will go to a temporary copy of the original file. Call
1451 1451 close() when you are done writing, and atomictempfile will rename
1452 1452 the temporary copy to the original name, making the changes
1453 1453 visible. If the object is destroyed without being closed, all your
1454 1454 writes are discarded.
1455 1455 '''
1456 def __init__(self, name, mode='w+b', createmode=None):
1456 def __init__(self, name, mode='w+b', createmode=None, checkambig=False):
1457 1457 self.__name = name # permanent name
1458 1458 self._tempname = mktempcopy(name, emptyok=('w' in mode),
1459 1459 createmode=createmode)
1460 1460 self._fp = posixfile(self._tempname, mode)
1461 self._checkambig = checkambig
1461 1462
1462 1463 # delegated methods
1463 1464 self.write = self._fp.write
1464 1465 self.seek = self._fp.seek
1465 1466 self.tell = self._fp.tell
1466 1467 self.fileno = self._fp.fileno
1467 1468
1468 1469 def close(self):
1469 1470 if not self._fp.closed:
1470 1471 self._fp.close()
1471 rename(self._tempname, localpath(self.__name))
1472 filename = localpath(self.__name)
1473 oldstat = self._checkambig and filestat(filename)
1474 if oldstat and oldstat.stat:
1475 rename(self._tempname, filename)
1476 newstat = filestat(filename)
1477 if newstat.isambig(oldstat):
1478 # stat of changed file is ambiguous to original one
1479 advanced = (oldstat.stat.st_mtime + 1) & 0x7fffffff
1480 os.utime(filename, (advanced, advanced))
1481 else:
1482 rename(self._tempname, filename)
1472 1483
1473 1484 def discard(self):
1474 1485 if not self._fp.closed:
1475 1486 try:
1476 1487 os.unlink(self._tempname)
1477 1488 except OSError:
1478 1489 pass
1479 1490 self._fp.close()
1480 1491
1481 1492 def __del__(self):
1482 1493 if safehasattr(self, '_fp'): # constructor actually did something
1483 1494 self.discard()
1484 1495
1485 1496 def makedirs(name, mode=None, notindexed=False):
1486 1497 """recursive directory creation with parent mode inheritance
1487 1498
1488 1499 Newly created directories are marked as "not to be indexed by
1489 1500 the content indexing service", if ``notindexed`` is specified
1490 1501 for "write" mode access.
1491 1502 """
1492 1503 try:
1493 1504 makedir(name, notindexed)
1494 1505 except OSError as err:
1495 1506 if err.errno == errno.EEXIST:
1496 1507 return
1497 1508 if err.errno != errno.ENOENT or not name:
1498 1509 raise
1499 1510 parent = os.path.dirname(os.path.abspath(name))
1500 1511 if parent == name:
1501 1512 raise
1502 1513 makedirs(parent, mode, notindexed)
1503 1514 try:
1504 1515 makedir(name, notindexed)
1505 1516 except OSError as err:
1506 1517 # Catch EEXIST to handle races
1507 1518 if err.errno == errno.EEXIST:
1508 1519 return
1509 1520 raise
1510 1521 if mode is not None:
1511 1522 os.chmod(name, mode)
1512 1523
1513 1524 def readfile(path):
1514 1525 with open(path, 'rb') as fp:
1515 1526 return fp.read()
1516 1527
1517 1528 def writefile(path, text):
1518 1529 with open(path, 'wb') as fp:
1519 1530 fp.write(text)
1520 1531
1521 1532 def appendfile(path, text):
1522 1533 with open(path, 'ab') as fp:
1523 1534 fp.write(text)
1524 1535
1525 1536 class chunkbuffer(object):
1526 1537 """Allow arbitrary sized chunks of data to be efficiently read from an
1527 1538 iterator over chunks of arbitrary size."""
1528 1539
1529 1540 def __init__(self, in_iter):
1530 1541 """in_iter is the iterator that's iterating over the input chunks.
1531 1542 targetsize is how big a buffer to try to maintain."""
1532 1543 def splitbig(chunks):
1533 1544 for chunk in chunks:
1534 1545 if len(chunk) > 2**20:
1535 1546 pos = 0
1536 1547 while pos < len(chunk):
1537 1548 end = pos + 2 ** 18
1538 1549 yield chunk[pos:end]
1539 1550 pos = end
1540 1551 else:
1541 1552 yield chunk
1542 1553 self.iter = splitbig(in_iter)
1543 1554 self._queue = collections.deque()
1544 1555 self._chunkoffset = 0
1545 1556
1546 1557 def read(self, l=None):
1547 1558 """Read L bytes of data from the iterator of chunks of data.
1548 1559 Returns less than L bytes if the iterator runs dry.
1549 1560
1550 1561 If size parameter is omitted, read everything"""
1551 1562 if l is None:
1552 1563 return ''.join(self.iter)
1553 1564
1554 1565 left = l
1555 1566 buf = []
1556 1567 queue = self._queue
1557 1568 while left > 0:
1558 1569 # refill the queue
1559 1570 if not queue:
1560 1571 target = 2**18
1561 1572 for chunk in self.iter:
1562 1573 queue.append(chunk)
1563 1574 target -= len(chunk)
1564 1575 if target <= 0:
1565 1576 break
1566 1577 if not queue:
1567 1578 break
1568 1579
1569 1580 # The easy way to do this would be to queue.popleft(), modify the
1570 1581 # chunk (if necessary), then queue.appendleft(). However, for cases
1571 1582 # where we read partial chunk content, this incurs 2 dequeue
1572 1583 # mutations and creates a new str for the remaining chunk in the
1573 1584 # queue. Our code below avoids this overhead.
1574 1585
1575 1586 chunk = queue[0]
1576 1587 chunkl = len(chunk)
1577 1588 offset = self._chunkoffset
1578 1589
1579 1590 # Use full chunk.
1580 1591 if offset == 0 and left >= chunkl:
1581 1592 left -= chunkl
1582 1593 queue.popleft()
1583 1594 buf.append(chunk)
1584 1595 # self._chunkoffset remains at 0.
1585 1596 continue
1586 1597
1587 1598 chunkremaining = chunkl - offset
1588 1599
1589 1600 # Use all of unconsumed part of chunk.
1590 1601 if left >= chunkremaining:
1591 1602 left -= chunkremaining
1592 1603 queue.popleft()
1593 1604 # offset == 0 is enabled by block above, so this won't merely
1594 1605 # copy via ``chunk[0:]``.
1595 1606 buf.append(chunk[offset:])
1596 1607 self._chunkoffset = 0
1597 1608
1598 1609 # Partial chunk needed.
1599 1610 else:
1600 1611 buf.append(chunk[offset:offset + left])
1601 1612 self._chunkoffset += left
1602 1613 left -= chunkremaining
1603 1614
1604 1615 return ''.join(buf)
1605 1616
1606 1617 def filechunkiter(f, size=65536, limit=None):
1607 1618 """Create a generator that produces the data in the file size
1608 1619 (default 65536) bytes at a time, up to optional limit (default is
1609 1620 to read all data). Chunks may be less than size bytes if the
1610 1621 chunk is the last chunk in the file, or the file is a socket or
1611 1622 some other type of file that sometimes reads less data than is
1612 1623 requested."""
1613 1624 assert size >= 0
1614 1625 assert limit is None or limit >= 0
1615 1626 while True:
1616 1627 if limit is None:
1617 1628 nbytes = size
1618 1629 else:
1619 1630 nbytes = min(limit, size)
1620 1631 s = nbytes and f.read(nbytes)
1621 1632 if not s:
1622 1633 break
1623 1634 if limit:
1624 1635 limit -= len(s)
1625 1636 yield s
1626 1637
1627 1638 def makedate(timestamp=None):
1628 1639 '''Return a unix timestamp (or the current time) as a (unixtime,
1629 1640 offset) tuple based off the local timezone.'''
1630 1641 if timestamp is None:
1631 1642 timestamp = time.time()
1632 1643 if timestamp < 0:
1633 1644 hint = _("check your clock")
1634 1645 raise Abort(_("negative timestamp: %d") % timestamp, hint=hint)
1635 1646 delta = (datetime.datetime.utcfromtimestamp(timestamp) -
1636 1647 datetime.datetime.fromtimestamp(timestamp))
1637 1648 tz = delta.days * 86400 + delta.seconds
1638 1649 return timestamp, tz
1639 1650
1640 1651 def datestr(date=None, format='%a %b %d %H:%M:%S %Y %1%2'):
1641 1652 """represent a (unixtime, offset) tuple as a localized time.
1642 1653 unixtime is seconds since the epoch, and offset is the time zone's
1643 1654 number of seconds away from UTC.
1644 1655
1645 1656 >>> datestr((0, 0))
1646 1657 'Thu Jan 01 00:00:00 1970 +0000'
1647 1658 >>> datestr((42, 0))
1648 1659 'Thu Jan 01 00:00:42 1970 +0000'
1649 1660 >>> datestr((-42, 0))
1650 1661 'Wed Dec 31 23:59:18 1969 +0000'
1651 1662 >>> datestr((0x7fffffff, 0))
1652 1663 'Tue Jan 19 03:14:07 2038 +0000'
1653 1664 >>> datestr((-0x80000000, 0))
1654 1665 'Fri Dec 13 20:45:52 1901 +0000'
1655 1666 """
1656 1667 t, tz = date or makedate()
1657 1668 if "%1" in format or "%2" in format or "%z" in format:
1658 1669 sign = (tz > 0) and "-" or "+"
1659 1670 minutes = abs(tz) // 60
1660 1671 q, r = divmod(minutes, 60)
1661 1672 format = format.replace("%z", "%1%2")
1662 1673 format = format.replace("%1", "%c%02d" % (sign, q))
1663 1674 format = format.replace("%2", "%02d" % r)
1664 1675 d = t - tz
1665 1676 if d > 0x7fffffff:
1666 1677 d = 0x7fffffff
1667 1678 elif d < -0x80000000:
1668 1679 d = -0x80000000
1669 1680 # Never use time.gmtime() and datetime.datetime.fromtimestamp()
1670 1681 # because they use the gmtime() system call which is buggy on Windows
1671 1682 # for negative values.
1672 1683 t = datetime.datetime(1970, 1, 1) + datetime.timedelta(seconds=d)
1673 1684 s = t.strftime(format)
1674 1685 return s
1675 1686
1676 1687 def shortdate(date=None):
1677 1688 """turn (timestamp, tzoff) tuple into iso 8631 date."""
1678 1689 return datestr(date, format='%Y-%m-%d')
1679 1690
1680 1691 def parsetimezone(tz):
1681 1692 """parse a timezone string and return an offset integer"""
1682 1693 if tz[0] in "+-" and len(tz) == 5 and tz[1:].isdigit():
1683 1694 sign = (tz[0] == "+") and 1 or -1
1684 1695 hours = int(tz[1:3])
1685 1696 minutes = int(tz[3:5])
1686 1697 return -sign * (hours * 60 + minutes) * 60
1687 1698 if tz == "GMT" or tz == "UTC":
1688 1699 return 0
1689 1700 return None
1690 1701
1691 1702 def strdate(string, format, defaults=[]):
1692 1703 """parse a localized time string and return a (unixtime, offset) tuple.
1693 1704 if the string cannot be parsed, ValueError is raised."""
1694 1705 # NOTE: unixtime = localunixtime + offset
1695 1706 offset, date = parsetimezone(string.split()[-1]), string
1696 1707 if offset is not None:
1697 1708 date = " ".join(string.split()[:-1])
1698 1709
1699 1710 # add missing elements from defaults
1700 1711 usenow = False # default to using biased defaults
1701 1712 for part in ("S", "M", "HI", "d", "mb", "yY"): # decreasing specificity
1702 1713 found = [True for p in part if ("%"+p) in format]
1703 1714 if not found:
1704 1715 date += "@" + defaults[part][usenow]
1705 1716 format += "@%" + part[0]
1706 1717 else:
1707 1718 # We've found a specific time element, less specific time
1708 1719 # elements are relative to today
1709 1720 usenow = True
1710 1721
1711 1722 timetuple = time.strptime(date, format)
1712 1723 localunixtime = int(calendar.timegm(timetuple))
1713 1724 if offset is None:
1714 1725 # local timezone
1715 1726 unixtime = int(time.mktime(timetuple))
1716 1727 offset = unixtime - localunixtime
1717 1728 else:
1718 1729 unixtime = localunixtime + offset
1719 1730 return unixtime, offset
1720 1731
1721 1732 def parsedate(date, formats=None, bias=None):
1722 1733 """parse a localized date/time and return a (unixtime, offset) tuple.
1723 1734
1724 1735 The date may be a "unixtime offset" string or in one of the specified
1725 1736 formats. If the date already is a (unixtime, offset) tuple, it is returned.
1726 1737
1727 1738 >>> parsedate(' today ') == parsedate(\
1728 1739 datetime.date.today().strftime('%b %d'))
1729 1740 True
1730 1741 >>> parsedate( 'yesterday ') == parsedate((datetime.date.today() -\
1731 1742 datetime.timedelta(days=1)\
1732 1743 ).strftime('%b %d'))
1733 1744 True
1734 1745 >>> now, tz = makedate()
1735 1746 >>> strnow, strtz = parsedate('now')
1736 1747 >>> (strnow - now) < 1
1737 1748 True
1738 1749 >>> tz == strtz
1739 1750 True
1740 1751 """
1741 1752 if bias is None:
1742 1753 bias = {}
1743 1754 if not date:
1744 1755 return 0, 0
1745 1756 if isinstance(date, tuple) and len(date) == 2:
1746 1757 return date
1747 1758 if not formats:
1748 1759 formats = defaultdateformats
1749 1760 date = date.strip()
1750 1761
1751 1762 if date == 'now' or date == _('now'):
1752 1763 return makedate()
1753 1764 if date == 'today' or date == _('today'):
1754 1765 date = datetime.date.today().strftime('%b %d')
1755 1766 elif date == 'yesterday' or date == _('yesterday'):
1756 1767 date = (datetime.date.today() -
1757 1768 datetime.timedelta(days=1)).strftime('%b %d')
1758 1769
1759 1770 try:
1760 1771 when, offset = map(int, date.split(' '))
1761 1772 except ValueError:
1762 1773 # fill out defaults
1763 1774 now = makedate()
1764 1775 defaults = {}
1765 1776 for part in ("d", "mb", "yY", "HI", "M", "S"):
1766 1777 # this piece is for rounding the specific end of unknowns
1767 1778 b = bias.get(part)
1768 1779 if b is None:
1769 1780 if part[0] in "HMS":
1770 1781 b = "00"
1771 1782 else:
1772 1783 b = "0"
1773 1784
1774 1785 # this piece is for matching the generic end to today's date
1775 1786 n = datestr(now, "%" + part[0])
1776 1787
1777 1788 defaults[part] = (b, n)
1778 1789
1779 1790 for format in formats:
1780 1791 try:
1781 1792 when, offset = strdate(date, format, defaults)
1782 1793 except (ValueError, OverflowError):
1783 1794 pass
1784 1795 else:
1785 1796 break
1786 1797 else:
1787 1798 raise Abort(_('invalid date: %r') % date)
1788 1799 # validate explicit (probably user-specified) date and
1789 1800 # time zone offset. values must fit in signed 32 bits for
1790 1801 # current 32-bit linux runtimes. timezones go from UTC-12
1791 1802 # to UTC+14
1792 1803 if when < -0x80000000 or when > 0x7fffffff:
1793 1804 raise Abort(_('date exceeds 32 bits: %d') % when)
1794 1805 if offset < -50400 or offset > 43200:
1795 1806 raise Abort(_('impossible time zone offset: %d') % offset)
1796 1807 return when, offset
1797 1808
1798 1809 def matchdate(date):
1799 1810 """Return a function that matches a given date match specifier
1800 1811
1801 1812 Formats include:
1802 1813
1803 1814 '{date}' match a given date to the accuracy provided
1804 1815
1805 1816 '<{date}' on or before a given date
1806 1817
1807 1818 '>{date}' on or after a given date
1808 1819
1809 1820 >>> p1 = parsedate("10:29:59")
1810 1821 >>> p2 = parsedate("10:30:00")
1811 1822 >>> p3 = parsedate("10:30:59")
1812 1823 >>> p4 = parsedate("10:31:00")
1813 1824 >>> p5 = parsedate("Sep 15 10:30:00 1999")
1814 1825 >>> f = matchdate("10:30")
1815 1826 >>> f(p1[0])
1816 1827 False
1817 1828 >>> f(p2[0])
1818 1829 True
1819 1830 >>> f(p3[0])
1820 1831 True
1821 1832 >>> f(p4[0])
1822 1833 False
1823 1834 >>> f(p5[0])
1824 1835 False
1825 1836 """
1826 1837
1827 1838 def lower(date):
1828 1839 d = {'mb': "1", 'd': "1"}
1829 1840 return parsedate(date, extendeddateformats, d)[0]
1830 1841
1831 1842 def upper(date):
1832 1843 d = {'mb': "12", 'HI': "23", 'M': "59", 'S': "59"}
1833 1844 for days in ("31", "30", "29"):
1834 1845 try:
1835 1846 d["d"] = days
1836 1847 return parsedate(date, extendeddateformats, d)[0]
1837 1848 except Abort:
1838 1849 pass
1839 1850 d["d"] = "28"
1840 1851 return parsedate(date, extendeddateformats, d)[0]
1841 1852
1842 1853 date = date.strip()
1843 1854
1844 1855 if not date:
1845 1856 raise Abort(_("dates cannot consist entirely of whitespace"))
1846 1857 elif date[0] == "<":
1847 1858 if not date[1:]:
1848 1859 raise Abort(_("invalid day spec, use '<DATE'"))
1849 1860 when = upper(date[1:])
1850 1861 return lambda x: x <= when
1851 1862 elif date[0] == ">":
1852 1863 if not date[1:]:
1853 1864 raise Abort(_("invalid day spec, use '>DATE'"))
1854 1865 when = lower(date[1:])
1855 1866 return lambda x: x >= when
1856 1867 elif date[0] == "-":
1857 1868 try:
1858 1869 days = int(date[1:])
1859 1870 except ValueError:
1860 1871 raise Abort(_("invalid day spec: %s") % date[1:])
1861 1872 if days < 0:
1862 1873 raise Abort(_('%s must be nonnegative (see "hg help dates")')
1863 1874 % date[1:])
1864 1875 when = makedate()[0] - days * 3600 * 24
1865 1876 return lambda x: x >= when
1866 1877 elif " to " in date:
1867 1878 a, b = date.split(" to ")
1868 1879 start, stop = lower(a), upper(b)
1869 1880 return lambda x: x >= start and x <= stop
1870 1881 else:
1871 1882 start, stop = lower(date), upper(date)
1872 1883 return lambda x: x >= start and x <= stop
1873 1884
1874 1885 def stringmatcher(pattern):
1875 1886 """
1876 1887 accepts a string, possibly starting with 're:' or 'literal:' prefix.
1877 1888 returns the matcher name, pattern, and matcher function.
1878 1889 missing or unknown prefixes are treated as literal matches.
1879 1890
1880 1891 helper for tests:
1881 1892 >>> def test(pattern, *tests):
1882 1893 ... kind, pattern, matcher = stringmatcher(pattern)
1883 1894 ... return (kind, pattern, [bool(matcher(t)) for t in tests])
1884 1895
1885 1896 exact matching (no prefix):
1886 1897 >>> test('abcdefg', 'abc', 'def', 'abcdefg')
1887 1898 ('literal', 'abcdefg', [False, False, True])
1888 1899
1889 1900 regex matching ('re:' prefix)
1890 1901 >>> test('re:a.+b', 'nomatch', 'fooadef', 'fooadefbar')
1891 1902 ('re', 'a.+b', [False, False, True])
1892 1903
1893 1904 force exact matches ('literal:' prefix)
1894 1905 >>> test('literal:re:foobar', 'foobar', 're:foobar')
1895 1906 ('literal', 're:foobar', [False, True])
1896 1907
1897 1908 unknown prefixes are ignored and treated as literals
1898 1909 >>> test('foo:bar', 'foo', 'bar', 'foo:bar')
1899 1910 ('literal', 'foo:bar', [False, False, True])
1900 1911 """
1901 1912 if pattern.startswith('re:'):
1902 1913 pattern = pattern[3:]
1903 1914 try:
1904 1915 regex = remod.compile(pattern)
1905 1916 except remod.error as e:
1906 1917 raise error.ParseError(_('invalid regular expression: %s')
1907 1918 % e)
1908 1919 return 're', pattern, regex.search
1909 1920 elif pattern.startswith('literal:'):
1910 1921 pattern = pattern[8:]
1911 1922 return 'literal', pattern, pattern.__eq__
1912 1923
1913 1924 def shortuser(user):
1914 1925 """Return a short representation of a user name or email address."""
1915 1926 f = user.find('@')
1916 1927 if f >= 0:
1917 1928 user = user[:f]
1918 1929 f = user.find('<')
1919 1930 if f >= 0:
1920 1931 user = user[f + 1:]
1921 1932 f = user.find(' ')
1922 1933 if f >= 0:
1923 1934 user = user[:f]
1924 1935 f = user.find('.')
1925 1936 if f >= 0:
1926 1937 user = user[:f]
1927 1938 return user
1928 1939
1929 1940 def emailuser(user):
1930 1941 """Return the user portion of an email address."""
1931 1942 f = user.find('@')
1932 1943 if f >= 0:
1933 1944 user = user[:f]
1934 1945 f = user.find('<')
1935 1946 if f >= 0:
1936 1947 user = user[f + 1:]
1937 1948 return user
1938 1949
1939 1950 def email(author):
1940 1951 '''get email of author.'''
1941 1952 r = author.find('>')
1942 1953 if r == -1:
1943 1954 r = None
1944 1955 return author[author.find('<') + 1:r]
1945 1956
1946 1957 def ellipsis(text, maxlength=400):
1947 1958 """Trim string to at most maxlength (default: 400) columns in display."""
1948 1959 return encoding.trim(text, maxlength, ellipsis='...')
1949 1960
1950 1961 def unitcountfn(*unittable):
1951 1962 '''return a function that renders a readable count of some quantity'''
1952 1963
1953 1964 def go(count):
1954 1965 for multiplier, divisor, format in unittable:
1955 1966 if count >= divisor * multiplier:
1956 1967 return format % (count / float(divisor))
1957 1968 return unittable[-1][2] % count
1958 1969
1959 1970 return go
1960 1971
1961 1972 bytecount = unitcountfn(
1962 1973 (100, 1 << 30, _('%.0f GB')),
1963 1974 (10, 1 << 30, _('%.1f GB')),
1964 1975 (1, 1 << 30, _('%.2f GB')),
1965 1976 (100, 1 << 20, _('%.0f MB')),
1966 1977 (10, 1 << 20, _('%.1f MB')),
1967 1978 (1, 1 << 20, _('%.2f MB')),
1968 1979 (100, 1 << 10, _('%.0f KB')),
1969 1980 (10, 1 << 10, _('%.1f KB')),
1970 1981 (1, 1 << 10, _('%.2f KB')),
1971 1982 (1, 1, _('%.0f bytes')),
1972 1983 )
1973 1984
1974 1985 def uirepr(s):
1975 1986 # Avoid double backslash in Windows path repr()
1976 1987 return repr(s).replace('\\\\', '\\')
1977 1988
1978 1989 # delay import of textwrap
1979 1990 def MBTextWrapper(**kwargs):
1980 1991 class tw(textwrap.TextWrapper):
1981 1992 """
1982 1993 Extend TextWrapper for width-awareness.
1983 1994
1984 1995 Neither number of 'bytes' in any encoding nor 'characters' is
1985 1996 appropriate to calculate terminal columns for specified string.
1986 1997
1987 1998 Original TextWrapper implementation uses built-in 'len()' directly,
1988 1999 so overriding is needed to use width information of each characters.
1989 2000
1990 2001 In addition, characters classified into 'ambiguous' width are
1991 2002 treated as wide in East Asian area, but as narrow in other.
1992 2003
1993 2004 This requires use decision to determine width of such characters.
1994 2005 """
1995 2006 def _cutdown(self, ucstr, space_left):
1996 2007 l = 0
1997 2008 colwidth = encoding.ucolwidth
1998 2009 for i in xrange(len(ucstr)):
1999 2010 l += colwidth(ucstr[i])
2000 2011 if space_left < l:
2001 2012 return (ucstr[:i], ucstr[i:])
2002 2013 return ucstr, ''
2003 2014
2004 2015 # overriding of base class
2005 2016 def _handle_long_word(self, reversed_chunks, cur_line, cur_len, width):
2006 2017 space_left = max(width - cur_len, 1)
2007 2018
2008 2019 if self.break_long_words:
2009 2020 cut, res = self._cutdown(reversed_chunks[-1], space_left)
2010 2021 cur_line.append(cut)
2011 2022 reversed_chunks[-1] = res
2012 2023 elif not cur_line:
2013 2024 cur_line.append(reversed_chunks.pop())
2014 2025
2015 2026 # this overriding code is imported from TextWrapper of Python 2.6
2016 2027 # to calculate columns of string by 'encoding.ucolwidth()'
2017 2028 def _wrap_chunks(self, chunks):
2018 2029 colwidth = encoding.ucolwidth
2019 2030
2020 2031 lines = []
2021 2032 if self.width <= 0:
2022 2033 raise ValueError("invalid width %r (must be > 0)" % self.width)
2023 2034
2024 2035 # Arrange in reverse order so items can be efficiently popped
2025 2036 # from a stack of chucks.
2026 2037 chunks.reverse()
2027 2038
2028 2039 while chunks:
2029 2040
2030 2041 # Start the list of chunks that will make up the current line.
2031 2042 # cur_len is just the length of all the chunks in cur_line.
2032 2043 cur_line = []
2033 2044 cur_len = 0
2034 2045
2035 2046 # Figure out which static string will prefix this line.
2036 2047 if lines:
2037 2048 indent = self.subsequent_indent
2038 2049 else:
2039 2050 indent = self.initial_indent
2040 2051
2041 2052 # Maximum width for this line.
2042 2053 width = self.width - len(indent)
2043 2054
2044 2055 # First chunk on line is whitespace -- drop it, unless this
2045 2056 # is the very beginning of the text (i.e. no lines started yet).
2046 2057 if self.drop_whitespace and chunks[-1].strip() == '' and lines:
2047 2058 del chunks[-1]
2048 2059
2049 2060 while chunks:
2050 2061 l = colwidth(chunks[-1])
2051 2062
2052 2063 # Can at least squeeze this chunk onto the current line.
2053 2064 if cur_len + l <= width:
2054 2065 cur_line.append(chunks.pop())
2055 2066 cur_len += l
2056 2067
2057 2068 # Nope, this line is full.
2058 2069 else:
2059 2070 break
2060 2071
2061 2072 # The current line is full, and the next chunk is too big to
2062 2073 # fit on *any* line (not just this one).
2063 2074 if chunks and colwidth(chunks[-1]) > width:
2064 2075 self._handle_long_word(chunks, cur_line, cur_len, width)
2065 2076
2066 2077 # If the last chunk on this line is all whitespace, drop it.
2067 2078 if (self.drop_whitespace and
2068 2079 cur_line and cur_line[-1].strip() == ''):
2069 2080 del cur_line[-1]
2070 2081
2071 2082 # Convert current line back to a string and store it in list
2072 2083 # of all lines (return value).
2073 2084 if cur_line:
2074 2085 lines.append(indent + ''.join(cur_line))
2075 2086
2076 2087 return lines
2077 2088
2078 2089 global MBTextWrapper
2079 2090 MBTextWrapper = tw
2080 2091 return tw(**kwargs)
2081 2092
2082 2093 def wrap(line, width, initindent='', hangindent=''):
2083 2094 maxindent = max(len(hangindent), len(initindent))
2084 2095 if width <= maxindent:
2085 2096 # adjust for weird terminal size
2086 2097 width = max(78, maxindent + 1)
2087 2098 line = line.decode(encoding.encoding, encoding.encodingmode)
2088 2099 initindent = initindent.decode(encoding.encoding, encoding.encodingmode)
2089 2100 hangindent = hangindent.decode(encoding.encoding, encoding.encodingmode)
2090 2101 wrapper = MBTextWrapper(width=width,
2091 2102 initial_indent=initindent,
2092 2103 subsequent_indent=hangindent)
2093 2104 return wrapper.fill(line).encode(encoding.encoding)
2094 2105
2095 2106 def iterlines(iterator):
2096 2107 for chunk in iterator:
2097 2108 for line in chunk.splitlines():
2098 2109 yield line
2099 2110
2100 2111 def expandpath(path):
2101 2112 return os.path.expanduser(os.path.expandvars(path))
2102 2113
2103 2114 def hgcmd():
2104 2115 """Return the command used to execute current hg
2105 2116
2106 2117 This is different from hgexecutable() because on Windows we want
2107 2118 to avoid things opening new shell windows like batch files, so we
2108 2119 get either the python call or current executable.
2109 2120 """
2110 2121 if mainfrozen():
2111 2122 if getattr(sys, 'frozen', None) == 'macosx_app':
2112 2123 # Env variable set by py2app
2113 2124 return [os.environ['EXECUTABLEPATH']]
2114 2125 else:
2115 2126 return [sys.executable]
2116 2127 return gethgcmd()
2117 2128
2118 2129 def rundetached(args, condfn):
2119 2130 """Execute the argument list in a detached process.
2120 2131
2121 2132 condfn is a callable which is called repeatedly and should return
2122 2133 True once the child process is known to have started successfully.
2123 2134 At this point, the child process PID is returned. If the child
2124 2135 process fails to start or finishes before condfn() evaluates to
2125 2136 True, return -1.
2126 2137 """
2127 2138 # Windows case is easier because the child process is either
2128 2139 # successfully starting and validating the condition or exiting
2129 2140 # on failure. We just poll on its PID. On Unix, if the child
2130 2141 # process fails to start, it will be left in a zombie state until
2131 2142 # the parent wait on it, which we cannot do since we expect a long
2132 2143 # running process on success. Instead we listen for SIGCHLD telling
2133 2144 # us our child process terminated.
2134 2145 terminated = set()
2135 2146 def handler(signum, frame):
2136 2147 terminated.add(os.wait())
2137 2148 prevhandler = None
2138 2149 SIGCHLD = getattr(signal, 'SIGCHLD', None)
2139 2150 if SIGCHLD is not None:
2140 2151 prevhandler = signal.signal(SIGCHLD, handler)
2141 2152 try:
2142 2153 pid = spawndetached(args)
2143 2154 while not condfn():
2144 2155 if ((pid in terminated or not testpid(pid))
2145 2156 and not condfn()):
2146 2157 return -1
2147 2158 time.sleep(0.1)
2148 2159 return pid
2149 2160 finally:
2150 2161 if prevhandler is not None:
2151 2162 signal.signal(signal.SIGCHLD, prevhandler)
2152 2163
2153 2164 def interpolate(prefix, mapping, s, fn=None, escape_prefix=False):
2154 2165 """Return the result of interpolating items in the mapping into string s.
2155 2166
2156 2167 prefix is a single character string, or a two character string with
2157 2168 a backslash as the first character if the prefix needs to be escaped in
2158 2169 a regular expression.
2159 2170
2160 2171 fn is an optional function that will be applied to the replacement text
2161 2172 just before replacement.
2162 2173
2163 2174 escape_prefix is an optional flag that allows using doubled prefix for
2164 2175 its escaping.
2165 2176 """
2166 2177 fn = fn or (lambda s: s)
2167 2178 patterns = '|'.join(mapping.keys())
2168 2179 if escape_prefix:
2169 2180 patterns += '|' + prefix
2170 2181 if len(prefix) > 1:
2171 2182 prefix_char = prefix[1:]
2172 2183 else:
2173 2184 prefix_char = prefix
2174 2185 mapping[prefix_char] = prefix_char
2175 2186 r = remod.compile(r'%s(%s)' % (prefix, patterns))
2176 2187 return r.sub(lambda x: fn(mapping[x.group()[1:]]), s)
2177 2188
2178 2189 def getport(port):
2179 2190 """Return the port for a given network service.
2180 2191
2181 2192 If port is an integer, it's returned as is. If it's a string, it's
2182 2193 looked up using socket.getservbyname(). If there's no matching
2183 2194 service, error.Abort is raised.
2184 2195 """
2185 2196 try:
2186 2197 return int(port)
2187 2198 except ValueError:
2188 2199 pass
2189 2200
2190 2201 try:
2191 2202 return socket.getservbyname(port)
2192 2203 except socket.error:
2193 2204 raise Abort(_("no port number associated with service '%s'") % port)
2194 2205
2195 2206 _booleans = {'1': True, 'yes': True, 'true': True, 'on': True, 'always': True,
2196 2207 '0': False, 'no': False, 'false': False, 'off': False,
2197 2208 'never': False}
2198 2209
2199 2210 def parsebool(s):
2200 2211 """Parse s into a boolean.
2201 2212
2202 2213 If s is not a valid boolean, returns None.
2203 2214 """
2204 2215 return _booleans.get(s.lower(), None)
2205 2216
2206 2217 _hexdig = '0123456789ABCDEFabcdef'
2207 2218 _hextochr = dict((a + b, chr(int(a + b, 16)))
2208 2219 for a in _hexdig for b in _hexdig)
2209 2220
2210 2221 def _urlunquote(s):
2211 2222 """Decode HTTP/HTML % encoding.
2212 2223
2213 2224 >>> _urlunquote('abc%20def')
2214 2225 'abc def'
2215 2226 """
2216 2227 res = s.split('%')
2217 2228 # fastpath
2218 2229 if len(res) == 1:
2219 2230 return s
2220 2231 s = res[0]
2221 2232 for item in res[1:]:
2222 2233 try:
2223 2234 s += _hextochr[item[:2]] + item[2:]
2224 2235 except KeyError:
2225 2236 s += '%' + item
2226 2237 except UnicodeDecodeError:
2227 2238 s += unichr(int(item[:2], 16)) + item[2:]
2228 2239 return s
2229 2240
2230 2241 class url(object):
2231 2242 r"""Reliable URL parser.
2232 2243
2233 2244 This parses URLs and provides attributes for the following
2234 2245 components:
2235 2246
2236 2247 <scheme>://<user>:<passwd>@<host>:<port>/<path>?<query>#<fragment>
2237 2248
2238 2249 Missing components are set to None. The only exception is
2239 2250 fragment, which is set to '' if present but empty.
2240 2251
2241 2252 If parsefragment is False, fragment is included in query. If
2242 2253 parsequery is False, query is included in path. If both are
2243 2254 False, both fragment and query are included in path.
2244 2255
2245 2256 See http://www.ietf.org/rfc/rfc2396.txt for more information.
2246 2257
2247 2258 Note that for backward compatibility reasons, bundle URLs do not
2248 2259 take host names. That means 'bundle://../' has a path of '../'.
2249 2260
2250 2261 Examples:
2251 2262
2252 2263 >>> url('http://www.ietf.org/rfc/rfc2396.txt')
2253 2264 <url scheme: 'http', host: 'www.ietf.org', path: 'rfc/rfc2396.txt'>
2254 2265 >>> url('ssh://[::1]:2200//home/joe/repo')
2255 2266 <url scheme: 'ssh', host: '[::1]', port: '2200', path: '/home/joe/repo'>
2256 2267 >>> url('file:///home/joe/repo')
2257 2268 <url scheme: 'file', path: '/home/joe/repo'>
2258 2269 >>> url('file:///c:/temp/foo/')
2259 2270 <url scheme: 'file', path: 'c:/temp/foo/'>
2260 2271 >>> url('bundle:foo')
2261 2272 <url scheme: 'bundle', path: 'foo'>
2262 2273 >>> url('bundle://../foo')
2263 2274 <url scheme: 'bundle', path: '../foo'>
2264 2275 >>> url(r'c:\foo\bar')
2265 2276 <url path: 'c:\\foo\\bar'>
2266 2277 >>> url(r'\\blah\blah\blah')
2267 2278 <url path: '\\\\blah\\blah\\blah'>
2268 2279 >>> url(r'\\blah\blah\blah#baz')
2269 2280 <url path: '\\\\blah\\blah\\blah', fragment: 'baz'>
2270 2281 >>> url(r'file:///C:\users\me')
2271 2282 <url scheme: 'file', path: 'C:\\users\\me'>
2272 2283
2273 2284 Authentication credentials:
2274 2285
2275 2286 >>> url('ssh://joe:xyz@x/repo')
2276 2287 <url scheme: 'ssh', user: 'joe', passwd: 'xyz', host: 'x', path: 'repo'>
2277 2288 >>> url('ssh://joe@x/repo')
2278 2289 <url scheme: 'ssh', user: 'joe', host: 'x', path: 'repo'>
2279 2290
2280 2291 Query strings and fragments:
2281 2292
2282 2293 >>> url('http://host/a?b#c')
2283 2294 <url scheme: 'http', host: 'host', path: 'a', query: 'b', fragment: 'c'>
2284 2295 >>> url('http://host/a?b#c', parsequery=False, parsefragment=False)
2285 2296 <url scheme: 'http', host: 'host', path: 'a?b#c'>
2286 2297 """
2287 2298
2288 2299 _safechars = "!~*'()+"
2289 2300 _safepchars = "/!~*'()+:\\"
2290 2301 _matchscheme = remod.compile(r'^[a-zA-Z0-9+.\-]+:').match
2291 2302
2292 2303 def __init__(self, path, parsequery=True, parsefragment=True):
2293 2304 # We slowly chomp away at path until we have only the path left
2294 2305 self.scheme = self.user = self.passwd = self.host = None
2295 2306 self.port = self.path = self.query = self.fragment = None
2296 2307 self._localpath = True
2297 2308 self._hostport = ''
2298 2309 self._origpath = path
2299 2310
2300 2311 if parsefragment and '#' in path:
2301 2312 path, self.fragment = path.split('#', 1)
2302 2313 if not path:
2303 2314 path = None
2304 2315
2305 2316 # special case for Windows drive letters and UNC paths
2306 2317 if hasdriveletter(path) or path.startswith(r'\\'):
2307 2318 self.path = path
2308 2319 return
2309 2320
2310 2321 # For compatibility reasons, we can't handle bundle paths as
2311 2322 # normal URLS
2312 2323 if path.startswith('bundle:'):
2313 2324 self.scheme = 'bundle'
2314 2325 path = path[7:]
2315 2326 if path.startswith('//'):
2316 2327 path = path[2:]
2317 2328 self.path = path
2318 2329 return
2319 2330
2320 2331 if self._matchscheme(path):
2321 2332 parts = path.split(':', 1)
2322 2333 if parts[0]:
2323 2334 self.scheme, path = parts
2324 2335 self._localpath = False
2325 2336
2326 2337 if not path:
2327 2338 path = None
2328 2339 if self._localpath:
2329 2340 self.path = ''
2330 2341 return
2331 2342 else:
2332 2343 if self._localpath:
2333 2344 self.path = path
2334 2345 return
2335 2346
2336 2347 if parsequery and '?' in path:
2337 2348 path, self.query = path.split('?', 1)
2338 2349 if not path:
2339 2350 path = None
2340 2351 if not self.query:
2341 2352 self.query = None
2342 2353
2343 2354 # // is required to specify a host/authority
2344 2355 if path and path.startswith('//'):
2345 2356 parts = path[2:].split('/', 1)
2346 2357 if len(parts) > 1:
2347 2358 self.host, path = parts
2348 2359 else:
2349 2360 self.host = parts[0]
2350 2361 path = None
2351 2362 if not self.host:
2352 2363 self.host = None
2353 2364 # path of file:///d is /d
2354 2365 # path of file:///d:/ is d:/, not /d:/
2355 2366 if path and not hasdriveletter(path):
2356 2367 path = '/' + path
2357 2368
2358 2369 if self.host and '@' in self.host:
2359 2370 self.user, self.host = self.host.rsplit('@', 1)
2360 2371 if ':' in self.user:
2361 2372 self.user, self.passwd = self.user.split(':', 1)
2362 2373 if not self.host:
2363 2374 self.host = None
2364 2375
2365 2376 # Don't split on colons in IPv6 addresses without ports
2366 2377 if (self.host and ':' in self.host and
2367 2378 not (self.host.startswith('[') and self.host.endswith(']'))):
2368 2379 self._hostport = self.host
2369 2380 self.host, self.port = self.host.rsplit(':', 1)
2370 2381 if not self.host:
2371 2382 self.host = None
2372 2383
2373 2384 if (self.host and self.scheme == 'file' and
2374 2385 self.host not in ('localhost', '127.0.0.1', '[::1]')):
2375 2386 raise Abort(_('file:// URLs can only refer to localhost'))
2376 2387
2377 2388 self.path = path
2378 2389
2379 2390 # leave the query string escaped
2380 2391 for a in ('user', 'passwd', 'host', 'port',
2381 2392 'path', 'fragment'):
2382 2393 v = getattr(self, a)
2383 2394 if v is not None:
2384 2395 setattr(self, a, _urlunquote(v))
2385 2396
2386 2397 def __repr__(self):
2387 2398 attrs = []
2388 2399 for a in ('scheme', 'user', 'passwd', 'host', 'port', 'path',
2389 2400 'query', 'fragment'):
2390 2401 v = getattr(self, a)
2391 2402 if v is not None:
2392 2403 attrs.append('%s: %r' % (a, v))
2393 2404 return '<url %s>' % ', '.join(attrs)
2394 2405
2395 2406 def __str__(self):
2396 2407 r"""Join the URL's components back into a URL string.
2397 2408
2398 2409 Examples:
2399 2410
2400 2411 >>> str(url('http://user:pw@host:80/c:/bob?fo:oo#ba:ar'))
2401 2412 'http://user:pw@host:80/c:/bob?fo:oo#ba:ar'
2402 2413 >>> str(url('http://user:pw@host:80/?foo=bar&baz=42'))
2403 2414 'http://user:pw@host:80/?foo=bar&baz=42'
2404 2415 >>> str(url('http://user:pw@host:80/?foo=bar%3dbaz'))
2405 2416 'http://user:pw@host:80/?foo=bar%3dbaz'
2406 2417 >>> str(url('ssh://user:pw@[::1]:2200//home/joe#'))
2407 2418 'ssh://user:pw@[::1]:2200//home/joe#'
2408 2419 >>> str(url('http://localhost:80//'))
2409 2420 'http://localhost:80//'
2410 2421 >>> str(url('http://localhost:80/'))
2411 2422 'http://localhost:80/'
2412 2423 >>> str(url('http://localhost:80'))
2413 2424 'http://localhost:80/'
2414 2425 >>> str(url('bundle:foo'))
2415 2426 'bundle:foo'
2416 2427 >>> str(url('bundle://../foo'))
2417 2428 'bundle:../foo'
2418 2429 >>> str(url('path'))
2419 2430 'path'
2420 2431 >>> str(url('file:///tmp/foo/bar'))
2421 2432 'file:///tmp/foo/bar'
2422 2433 >>> str(url('file:///c:/tmp/foo/bar'))
2423 2434 'file:///c:/tmp/foo/bar'
2424 2435 >>> print url(r'bundle:foo\bar')
2425 2436 bundle:foo\bar
2426 2437 >>> print url(r'file:///D:\data\hg')
2427 2438 file:///D:\data\hg
2428 2439 """
2429 2440 if self._localpath:
2430 2441 s = self.path
2431 2442 if self.scheme == 'bundle':
2432 2443 s = 'bundle:' + s
2433 2444 if self.fragment:
2434 2445 s += '#' + self.fragment
2435 2446 return s
2436 2447
2437 2448 s = self.scheme + ':'
2438 2449 if self.user or self.passwd or self.host:
2439 2450 s += '//'
2440 2451 elif self.scheme and (not self.path or self.path.startswith('/')
2441 2452 or hasdriveletter(self.path)):
2442 2453 s += '//'
2443 2454 if hasdriveletter(self.path):
2444 2455 s += '/'
2445 2456 if self.user:
2446 2457 s += urlreq.quote(self.user, safe=self._safechars)
2447 2458 if self.passwd:
2448 2459 s += ':' + urlreq.quote(self.passwd, safe=self._safechars)
2449 2460 if self.user or self.passwd:
2450 2461 s += '@'
2451 2462 if self.host:
2452 2463 if not (self.host.startswith('[') and self.host.endswith(']')):
2453 2464 s += urlreq.quote(self.host)
2454 2465 else:
2455 2466 s += self.host
2456 2467 if self.port:
2457 2468 s += ':' + urlreq.quote(self.port)
2458 2469 if self.host:
2459 2470 s += '/'
2460 2471 if self.path:
2461 2472 # TODO: similar to the query string, we should not unescape the
2462 2473 # path when we store it, the path might contain '%2f' = '/',
2463 2474 # which we should *not* escape.
2464 2475 s += urlreq.quote(self.path, safe=self._safepchars)
2465 2476 if self.query:
2466 2477 # we store the query in escaped form.
2467 2478 s += '?' + self.query
2468 2479 if self.fragment is not None:
2469 2480 s += '#' + urlreq.quote(self.fragment, safe=self._safepchars)
2470 2481 return s
2471 2482
2472 2483 def authinfo(self):
2473 2484 user, passwd = self.user, self.passwd
2474 2485 try:
2475 2486 self.user, self.passwd = None, None
2476 2487 s = str(self)
2477 2488 finally:
2478 2489 self.user, self.passwd = user, passwd
2479 2490 if not self.user:
2480 2491 return (s, None)
2481 2492 # authinfo[1] is passed to urllib2 password manager, and its
2482 2493 # URIs must not contain credentials. The host is passed in the
2483 2494 # URIs list because Python < 2.4.3 uses only that to search for
2484 2495 # a password.
2485 2496 return (s, (None, (s, self.host),
2486 2497 self.user, self.passwd or ''))
2487 2498
2488 2499 def isabs(self):
2489 2500 if self.scheme and self.scheme != 'file':
2490 2501 return True # remote URL
2491 2502 if hasdriveletter(self.path):
2492 2503 return True # absolute for our purposes - can't be joined()
2493 2504 if self.path.startswith(r'\\'):
2494 2505 return True # Windows UNC path
2495 2506 if self.path.startswith('/'):
2496 2507 return True # POSIX-style
2497 2508 return False
2498 2509
2499 2510 def localpath(self):
2500 2511 if self.scheme == 'file' or self.scheme == 'bundle':
2501 2512 path = self.path or '/'
2502 2513 # For Windows, we need to promote hosts containing drive
2503 2514 # letters to paths with drive letters.
2504 2515 if hasdriveletter(self._hostport):
2505 2516 path = self._hostport + '/' + self.path
2506 2517 elif (self.host is not None and self.path
2507 2518 and not hasdriveletter(path)):
2508 2519 path = '/' + path
2509 2520 return path
2510 2521 return self._origpath
2511 2522
2512 2523 def islocal(self):
2513 2524 '''whether localpath will return something that posixfile can open'''
2514 2525 return (not self.scheme or self.scheme == 'file'
2515 2526 or self.scheme == 'bundle')
2516 2527
2517 2528 def hasscheme(path):
2518 2529 return bool(url(path).scheme)
2519 2530
2520 2531 def hasdriveletter(path):
2521 2532 return path and path[1:2] == ':' and path[0:1].isalpha()
2522 2533
2523 2534 def urllocalpath(path):
2524 2535 return url(path, parsequery=False, parsefragment=False).localpath()
2525 2536
2526 2537 def hidepassword(u):
2527 2538 '''hide user credential in a url string'''
2528 2539 u = url(u)
2529 2540 if u.passwd:
2530 2541 u.passwd = '***'
2531 2542 return str(u)
2532 2543
2533 2544 def removeauth(u):
2534 2545 '''remove all authentication information from a url string'''
2535 2546 u = url(u)
2536 2547 u.user = u.passwd = None
2537 2548 return str(u)
2538 2549
2539 2550 def isatty(fp):
2540 2551 try:
2541 2552 return fp.isatty()
2542 2553 except AttributeError:
2543 2554 return False
2544 2555
2545 2556 timecount = unitcountfn(
2546 2557 (1, 1e3, _('%.0f s')),
2547 2558 (100, 1, _('%.1f s')),
2548 2559 (10, 1, _('%.2f s')),
2549 2560 (1, 1, _('%.3f s')),
2550 2561 (100, 0.001, _('%.1f ms')),
2551 2562 (10, 0.001, _('%.2f ms')),
2552 2563 (1, 0.001, _('%.3f ms')),
2553 2564 (100, 0.000001, _('%.1f us')),
2554 2565 (10, 0.000001, _('%.2f us')),
2555 2566 (1, 0.000001, _('%.3f us')),
2556 2567 (100, 0.000000001, _('%.1f ns')),
2557 2568 (10, 0.000000001, _('%.2f ns')),
2558 2569 (1, 0.000000001, _('%.3f ns')),
2559 2570 )
2560 2571
2561 2572 _timenesting = [0]
2562 2573
2563 2574 def timed(func):
2564 2575 '''Report the execution time of a function call to stderr.
2565 2576
2566 2577 During development, use as a decorator when you need to measure
2567 2578 the cost of a function, e.g. as follows:
2568 2579
2569 2580 @util.timed
2570 2581 def foo(a, b, c):
2571 2582 pass
2572 2583 '''
2573 2584
2574 2585 def wrapper(*args, **kwargs):
2575 2586 start = time.time()
2576 2587 indent = 2
2577 2588 _timenesting[0] += indent
2578 2589 try:
2579 2590 return func(*args, **kwargs)
2580 2591 finally:
2581 2592 elapsed = time.time() - start
2582 2593 _timenesting[0] -= indent
2583 2594 sys.stderr.write('%s%s: %s\n' %
2584 2595 (' ' * _timenesting[0], func.__name__,
2585 2596 timecount(elapsed)))
2586 2597 return wrapper
2587 2598
2588 2599 _sizeunits = (('m', 2**20), ('k', 2**10), ('g', 2**30),
2589 2600 ('kb', 2**10), ('mb', 2**20), ('gb', 2**30), ('b', 1))
2590 2601
2591 2602 def sizetoint(s):
2592 2603 '''Convert a space specifier to a byte count.
2593 2604
2594 2605 >>> sizetoint('30')
2595 2606 30
2596 2607 >>> sizetoint('2.2kb')
2597 2608 2252
2598 2609 >>> sizetoint('6M')
2599 2610 6291456
2600 2611 '''
2601 2612 t = s.strip().lower()
2602 2613 try:
2603 2614 for k, u in _sizeunits:
2604 2615 if t.endswith(k):
2605 2616 return int(float(t[:-len(k)]) * u)
2606 2617 return int(t)
2607 2618 except ValueError:
2608 2619 raise error.ParseError(_("couldn't parse size: %s") % s)
2609 2620
2610 2621 class hooks(object):
2611 2622 '''A collection of hook functions that can be used to extend a
2612 2623 function's behavior. Hooks are called in lexicographic order,
2613 2624 based on the names of their sources.'''
2614 2625
2615 2626 def __init__(self):
2616 2627 self._hooks = []
2617 2628
2618 2629 def add(self, source, hook):
2619 2630 self._hooks.append((source, hook))
2620 2631
2621 2632 def __call__(self, *args):
2622 2633 self._hooks.sort(key=lambda x: x[0])
2623 2634 results = []
2624 2635 for source, hook in self._hooks:
2625 2636 results.append(hook(*args))
2626 2637 return results
2627 2638
2628 2639 def getstackframes(skip=0, line=' %-*s in %s\n', fileline='%s:%s'):
2629 2640 '''Yields lines for a nicely formatted stacktrace.
2630 2641 Skips the 'skip' last entries.
2631 2642 Each file+linenumber is formatted according to fileline.
2632 2643 Each line is formatted according to line.
2633 2644 If line is None, it yields:
2634 2645 length of longest filepath+line number,
2635 2646 filepath+linenumber,
2636 2647 function
2637 2648
2638 2649 Not be used in production code but very convenient while developing.
2639 2650 '''
2640 2651 entries = [(fileline % (fn, ln), func)
2641 2652 for fn, ln, func, _text in traceback.extract_stack()[:-skip - 1]]
2642 2653 if entries:
2643 2654 fnmax = max(len(entry[0]) for entry in entries)
2644 2655 for fnln, func in entries:
2645 2656 if line is None:
2646 2657 yield (fnmax, fnln, func)
2647 2658 else:
2648 2659 yield line % (fnmax, fnln, func)
2649 2660
2650 2661 def debugstacktrace(msg='stacktrace', skip=0, f=sys.stderr, otherf=sys.stdout):
2651 2662 '''Writes a message to f (stderr) with a nicely formatted stacktrace.
2652 2663 Skips the 'skip' last entries. By default it will flush stdout first.
2653 2664 It can be used everywhere and intentionally does not require an ui object.
2654 2665 Not be used in production code but very convenient while developing.
2655 2666 '''
2656 2667 if otherf:
2657 2668 otherf.flush()
2658 2669 f.write('%s at:\n' % msg)
2659 2670 for line in getstackframes(skip + 1):
2660 2671 f.write(line)
2661 2672 f.flush()
2662 2673
2663 2674 class dirs(object):
2664 2675 '''a multiset of directory names from a dirstate or manifest'''
2665 2676
2666 2677 def __init__(self, map, skip=None):
2667 2678 self._dirs = {}
2668 2679 addpath = self.addpath
2669 2680 if safehasattr(map, 'iteritems') and skip is not None:
2670 2681 for f, s in map.iteritems():
2671 2682 if s[0] != skip:
2672 2683 addpath(f)
2673 2684 else:
2674 2685 for f in map:
2675 2686 addpath(f)
2676 2687
2677 2688 def addpath(self, path):
2678 2689 dirs = self._dirs
2679 2690 for base in finddirs(path):
2680 2691 if base in dirs:
2681 2692 dirs[base] += 1
2682 2693 return
2683 2694 dirs[base] = 1
2684 2695
2685 2696 def delpath(self, path):
2686 2697 dirs = self._dirs
2687 2698 for base in finddirs(path):
2688 2699 if dirs[base] > 1:
2689 2700 dirs[base] -= 1
2690 2701 return
2691 2702 del dirs[base]
2692 2703
2693 2704 def __iter__(self):
2694 2705 return self._dirs.iterkeys()
2695 2706
2696 2707 def __contains__(self, d):
2697 2708 return d in self._dirs
2698 2709
2699 2710 if safehasattr(parsers, 'dirs'):
2700 2711 dirs = parsers.dirs
2701 2712
2702 2713 def finddirs(path):
2703 2714 pos = path.rfind('/')
2704 2715 while pos != -1:
2705 2716 yield path[:pos]
2706 2717 pos = path.rfind('/', 0, pos)
2707 2718
2708 2719 # compression utility
2709 2720
2710 2721 class nocompress(object):
2711 2722 def compress(self, x):
2712 2723 return x
2713 2724 def flush(self):
2714 2725 return ""
2715 2726
2716 2727 compressors = {
2717 2728 None: nocompress,
2718 2729 # lambda to prevent early import
2719 2730 'BZ': lambda: bz2.BZ2Compressor(),
2720 2731 'GZ': lambda: zlib.compressobj(),
2721 2732 }
2722 2733 # also support the old form by courtesies
2723 2734 compressors['UN'] = compressors[None]
2724 2735
2725 2736 def _makedecompressor(decompcls):
2726 2737 def generator(f):
2727 2738 d = decompcls()
2728 2739 for chunk in filechunkiter(f):
2729 2740 yield d.decompress(chunk)
2730 2741 def func(fh):
2731 2742 return chunkbuffer(generator(fh))
2732 2743 return func
2733 2744
2734 2745 class ctxmanager(object):
2735 2746 '''A context manager for use in 'with' blocks to allow multiple
2736 2747 contexts to be entered at once. This is both safer and more
2737 2748 flexible than contextlib.nested.
2738 2749
2739 2750 Once Mercurial supports Python 2.7+, this will become mostly
2740 2751 unnecessary.
2741 2752 '''
2742 2753
2743 2754 def __init__(self, *args):
2744 2755 '''Accepts a list of no-argument functions that return context
2745 2756 managers. These will be invoked at __call__ time.'''
2746 2757 self._pending = args
2747 2758 self._atexit = []
2748 2759
2749 2760 def __enter__(self):
2750 2761 return self
2751 2762
2752 2763 def enter(self):
2753 2764 '''Create and enter context managers in the order in which they were
2754 2765 passed to the constructor.'''
2755 2766 values = []
2756 2767 for func in self._pending:
2757 2768 obj = func()
2758 2769 values.append(obj.__enter__())
2759 2770 self._atexit.append(obj.__exit__)
2760 2771 del self._pending
2761 2772 return values
2762 2773
2763 2774 def atexit(self, func, *args, **kwargs):
2764 2775 '''Add a function to call when this context manager exits. The
2765 2776 ordering of multiple atexit calls is unspecified, save that
2766 2777 they will happen before any __exit__ functions.'''
2767 2778 def wrapper(exc_type, exc_val, exc_tb):
2768 2779 func(*args, **kwargs)
2769 2780 self._atexit.append(wrapper)
2770 2781 return func
2771 2782
2772 2783 def __exit__(self, exc_type, exc_val, exc_tb):
2773 2784 '''Context managers are exited in the reverse order from which
2774 2785 they were created.'''
2775 2786 received = exc_type is not None
2776 2787 suppressed = False
2777 2788 pending = None
2778 2789 self._atexit.reverse()
2779 2790 for exitfunc in self._atexit:
2780 2791 try:
2781 2792 if exitfunc(exc_type, exc_val, exc_tb):
2782 2793 suppressed = True
2783 2794 exc_type = None
2784 2795 exc_val = None
2785 2796 exc_tb = None
2786 2797 except BaseException:
2787 2798 pending = sys.exc_info()
2788 2799 exc_type, exc_val, exc_tb = pending = sys.exc_info()
2789 2800 del self._atexit
2790 2801 if pending:
2791 2802 raise exc_val
2792 2803 return received and suppressed
2793 2804
2794 2805 def _bz2():
2795 2806 d = bz2.BZ2Decompressor()
2796 2807 # Bzip2 stream start with BZ, but we stripped it.
2797 2808 # we put it back for good measure.
2798 2809 d.decompress('BZ')
2799 2810 return d
2800 2811
2801 2812 decompressors = {None: lambda fh: fh,
2802 2813 '_truncatedBZ': _makedecompressor(_bz2),
2803 2814 'BZ': _makedecompressor(lambda: bz2.BZ2Decompressor()),
2804 2815 'GZ': _makedecompressor(lambda: zlib.decompressobj()),
2805 2816 }
2806 2817 # also support the old form by courtesies
2807 2818 decompressors['UN'] = decompressors[None]
2808 2819
2809 2820 # convenient shortcut
2810 2821 dst = debugstacktrace
@@ -1,47 +1,87 b''
1 1 from __future__ import absolute_import
2 2
3 3 import glob
4 4 import os
5 5 import unittest
6 6
7 7 from mercurial import (
8 8 util,
9 9 )
10 10 atomictempfile = util.atomictempfile
11 11
12 12 class testatomictempfile(unittest.TestCase):
13 13 def test1_simple(self):
14 14 if os.path.exists('foo'):
15 15 os.remove('foo')
16 16 file = atomictempfile('foo')
17 17 (dir, basename) = os.path.split(file._tempname)
18 18 self.assertFalse(os.path.isfile('foo'))
19 19 self.assertTrue(basename in glob.glob('.foo-*'))
20 20
21 21 file.write(b'argh\n')
22 22 file.close()
23 23
24 24 self.assertTrue(os.path.isfile('foo'))
25 25 self.assertTrue(basename not in glob.glob('.foo-*'))
26 26
27 27 # discard() removes the temp file without making the write permanent
28 28 def test2_discard(self):
29 29 if os.path.exists('foo'):
30 30 os.remove('foo')
31 31 file = atomictempfile('foo')
32 32 (dir, basename) = os.path.split(file._tempname)
33 33
34 34 file.write(b'yo\n')
35 35 file.discard()
36 36
37 37 self.assertFalse(os.path.isfile('foo'))
38 38 self.assertTrue(basename not in os.listdir('.'))
39 39
40 40 # if a programmer screws up and passes bad args to atomictempfile, they
41 41 # get a plain ordinary TypeError, not infinite recursion
42 42 def test3_oops(self):
43 43 self.assertRaises(TypeError, atomictempfile)
44 44
45 # checkambig=True avoids ambiguity of timestamp
46 def test4_checkambig(self):
47 def atomicwrite(checkambig):
48 f = atomictempfile('foo', checkambig=checkambig)
49 f.write('FOO')
50 f.close()
51
52 # try some times, because reproduction of ambiguity depends on
53 # "filesystem time"
54 for i in xrange(5):
55 atomicwrite(False)
56 oldstat = os.stat('foo')
57 if oldstat.st_ctime != oldstat.st_mtime:
58 # subsequent changing never causes ambiguity
59 continue
60
61 repetition = 3
62
63 # repeat atomic write with checkambig=True, to examine
64 # whether st_mtime is advanced multiple times as expecetd
65 for j in xrange(repetition):
66 atomicwrite(True)
67 newstat = os.stat('foo')
68 if oldstat.st_ctime != newstat.st_ctime:
69 # timestamp ambiguity was naturally avoided while repetition
70 continue
71
72 # st_mtime should be advanced "repetition" times, because
73 # all atomicwrite() occured at same time (in sec)
74 self.assertTrue(newstat.st_mtime ==
75 ((oldstat.st_mtime + repetition) & 0x7fffffff))
76 # no more examination is needed, if assumption above is true
77 break
78 else:
79 # This platform seems too slow to examine anti-ambiguity
80 # of file timestamp (or test happened to be executed at
81 # bad timing). Exit silently in this case, because running
82 # on other faster platforms can detect problems
83 pass
84
45 85 if __name__ == '__main__':
46 86 import silenttestrunner
47 87 silenttestrunner.main(__name__)
General Comments 0
You need to be logged in to leave comments. Login now