~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/osutils.py

  • Committer: John Arbash Meinel
  • Date: 2010-02-17 17:11:16 UTC
  • mfrom: (4797.2.17 2.1)
  • mto: (4797.2.18 2.1)
  • mto: This revision was merged to the branch mainline in revision 5055.
  • Revision ID: john@arbash-meinel.com-20100217171116-h7t9223ystbnx5h8
merge bzr.2.1 in preparation for NEWS entry.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2006, 2007, 2009 Canonical Ltd
 
1
# Copyright (C) 2005-2010 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
39
39
from shutil import (
40
40
    rmtree,
41
41
    )
 
42
import signal
42
43
import subprocess
43
44
import tempfile
44
45
from tempfile import (
71
72
from bzrlib import symbol_versioning
72
73
 
73
74
 
 
75
# Cross platform wall-clock time functionality with decent resolution.
 
76
# On Linux ``time.clock`` returns only CPU time. On Windows, ``time.time()``
 
77
# only has a resolution of ~15ms. Note that ``time.clock()`` is not
 
78
# synchronized with ``time.time()``, this is only meant to be used to find
 
79
# delta times by subtracting from another call to this function.
 
80
timer_func = time.time
 
81
if sys.platform == 'win32':
 
82
    timer_func = time.clock
 
83
 
74
84
# On win32, O_BINARY is used to indicate the file should
75
85
# be opened in binary mode, rather than text mode.
76
86
# On other platforms, O_BINARY doesn't exist, because
192
202
    :param old: The old path, to rename from
193
203
    :param new: The new path, to rename to
194
204
    :param rename_func: The potentially non-atomic rename function
195
 
    :param unlink_func: A way to delete the target file if the full rename succeeds
 
205
    :param unlink_func: A way to delete the target file if the full rename
 
206
        succeeds
196
207
    """
197
 
 
198
208
    # sftp rename doesn't allow overwriting, so play tricks:
199
209
    base = os.path.basename(new)
200
210
    dirname = os.path.dirname(new)
201
 
    tmp_name = u'tmp.%s.%.9f.%d.%s' % (base, time.time(), os.getpid(), rand_chars(10))
 
211
    # callers use different encodings for the paths so the following MUST
 
212
    # respect that. We rely on python upcasting to unicode if new is unicode
 
213
    # and keeping a str if not.
 
214
    tmp_name = 'tmp.%s.%.9f.%d.%s' % (base, time.time(),
 
215
                                      os.getpid(), rand_chars(10))
202
216
    tmp_name = pathjoin(dirname, tmp_name)
203
217
 
204
218
    # Rename the file out of the way, but keep track if it didn't exist
224
238
    else:
225
239
        file_existed = True
226
240
 
 
241
    failure_exc = None
227
242
    success = False
228
243
    try:
229
244
        try:
235
250
            # source and target may be aliases of each other (e.g. on a
236
251
            # case-insensitive filesystem), so we may have accidentally renamed
237
252
            # source by when we tried to rename target
238
 
            if not (file_existed and e.errno in (None, errno.ENOENT)):
239
 
                raise
 
253
            failure_exc = sys.exc_info()
 
254
            if (file_existed and e.errno in (None, errno.ENOENT)
 
255
                and old.lower() == new.lower()):
 
256
                # source and target are the same file on a case-insensitive
 
257
                # filesystem, so we don't generate an exception
 
258
                failure_exc = None
240
259
    finally:
241
260
        if file_existed:
242
261
            # If the file used to exist, rename it back into place
245
264
                unlink_func(tmp_name)
246
265
            else:
247
266
                rename_func(tmp_name, new)
 
267
    if failure_exc is not None:
 
268
        raise failure_exc[0], failure_exc[1], failure_exc[2]
248
269
 
249
270
 
250
271
# In Python 2.4.2 and older, os.path.abspath and os.path.realpath
688
709
    return offset.days * 86400 + offset.seconds
689
710
 
690
711
weekdays = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
 
712
_default_format_by_weekday_num = [wd + " %Y-%m-%d %H:%M:%S" for wd in weekdays]
 
713
 
691
714
 
692
715
def format_date(t, offset=0, timezone='original', date_fmt=None,
693
716
                show_offset=True):
707
730
    date_str = time.strftime(date_fmt, tt)
708
731
    return date_str + offset_str
709
732
 
 
733
 
 
734
# Cache of formatted offset strings
 
735
_offset_cache = {}
 
736
 
 
737
 
 
738
def format_date_with_offset_in_original_timezone(t, offset=0,
 
739
    _cache=_offset_cache):
 
740
    """Return a formatted date string in the original timezone.
 
741
 
 
742
    This routine may be faster then format_date.
 
743
 
 
744
    :param t: Seconds since the epoch.
 
745
    :param offset: Timezone offset in seconds east of utc.
 
746
    """
 
747
    if offset is None:
 
748
        offset = 0
 
749
    tt = time.gmtime(t + offset)
 
750
    date_fmt = _default_format_by_weekday_num[tt[6]]
 
751
    date_str = time.strftime(date_fmt, tt)
 
752
    offset_str = _cache.get(offset, None)
 
753
    if offset_str is None:
 
754
        offset_str = ' %+03d%02d' % (offset / 3600, (offset / 60) % 60)
 
755
        _cache[offset] = offset_str
 
756
    return date_str + offset_str
 
757
 
 
758
 
710
759
def format_local_date(t, offset=0, timezone='original', date_fmt=None,
711
760
                      show_offset=True):
712
761
    """Return an unicode date string formatted according to the current locale.
726
775
        date_str = date_str.decode(get_user_encoding(), 'replace')
727
776
    return date_str + offset_str
728
777
 
 
778
 
729
779
def _format_date(t, offset, timezone, date_fmt, show_offset):
730
780
    if timezone == 'utc':
731
781
        tt = time.gmtime(t)
1294
1344
    normalized_filename = _inaccessible_normalized_filename
1295
1345
 
1296
1346
 
 
1347
default_terminal_width = 80
 
1348
"""The default terminal width for ttys.
 
1349
 
 
1350
This is defined so that higher levels can share a common fallback value when
 
1351
terminal_width() returns None.
 
1352
"""
 
1353
 
 
1354
 
1297
1355
def terminal_width():
1298
 
    """Return estimated terminal width."""
1299
 
    if sys.platform == 'win32':
1300
 
        return win32utils.get_console_size()[0]
1301
 
    width = 0
 
1356
    """Return terminal width.
 
1357
 
 
1358
    None is returned if the width can't established precisely.
 
1359
 
 
1360
    The rules are:
 
1361
    - if BZR_COLUMNS is set, returns its value
 
1362
    - if there is no controlling terminal, returns None
 
1363
    - if COLUMNS is set, returns its value,
 
1364
 
 
1365
    From there, we need to query the OS to get the size of the controlling
 
1366
    terminal.
 
1367
 
 
1368
    Unices:
 
1369
    - get termios.TIOCGWINSZ
 
1370
    - if an error occurs or a negative value is obtained, returns None
 
1371
 
 
1372
    Windows:
 
1373
    
 
1374
    - win32utils.get_console_size() decides,
 
1375
    - returns None on error (provided default value)
 
1376
    """
 
1377
 
 
1378
    # If BZR_COLUMNS is set, take it, user is always right
 
1379
    try:
 
1380
        return int(os.environ['BZR_COLUMNS'])
 
1381
    except (KeyError, ValueError):
 
1382
        pass
 
1383
 
 
1384
    isatty = getattr(sys.stdout, 'isatty', None)
 
1385
    if  isatty is None or not isatty():
 
1386
        # Don't guess, setting BZR_COLUMNS is the recommended way to override.
 
1387
        return None
 
1388
 
 
1389
    # If COLUMNS is set, take it, the terminal knows better (even inside a
 
1390
    # given terminal, the application can decide to set COLUMNS to a lower
 
1391
    # value (splitted screen) or a bigger value (scroll bars))
 
1392
    try:
 
1393
        return int(os.environ['COLUMNS'])
 
1394
    except (KeyError, ValueError):
 
1395
        pass
 
1396
 
 
1397
    width, height = _terminal_size(None, None)
 
1398
    if width <= 0:
 
1399
        # Consider invalid values as meaning no width
 
1400
        return None
 
1401
 
 
1402
    return width
 
1403
 
 
1404
 
 
1405
def _win32_terminal_size(width, height):
 
1406
    width, height = win32utils.get_console_size(defaultx=width, defaulty=height)
 
1407
    return width, height
 
1408
 
 
1409
 
 
1410
def _ioctl_terminal_size(width, height):
1302
1411
    try:
1303
1412
        import struct, fcntl, termios
1304
1413
        s = struct.pack('HHHH', 0, 0, 0, 0)
1305
1414
        x = fcntl.ioctl(1, termios.TIOCGWINSZ, s)
1306
 
        width = struct.unpack('HHHH', x)[1]
1307
 
    except IOError:
 
1415
        height, width = struct.unpack('HHHH', x)[0:2]
 
1416
    except (IOError, AttributeError):
1308
1417
        pass
1309
 
    if width <= 0:
1310
 
        try:
1311
 
            width = int(os.environ['COLUMNS'])
1312
 
        except:
 
1418
    return width, height
 
1419
 
 
1420
_terminal_size = None
 
1421
"""Returns the terminal size as (width, height).
 
1422
 
 
1423
:param width: Default value for width.
 
1424
:param height: Default value for height.
 
1425
 
 
1426
This is defined specifically for each OS and query the size of the controlling
 
1427
terminal. If any error occurs, the provided default values should be returned.
 
1428
"""
 
1429
if sys.platform == 'win32':
 
1430
    _terminal_size = _win32_terminal_size
 
1431
else:
 
1432
    _terminal_size = _ioctl_terminal_size
 
1433
 
 
1434
 
 
1435
def _terminal_size_changed(signum, frame):
 
1436
    """Set COLUMNS upon receiving a SIGnal for WINdow size CHange."""
 
1437
    width, height = _terminal_size(None, None)
 
1438
    if width is not None:
 
1439
        os.environ['COLUMNS'] = str(width)
 
1440
 
 
1441
 
 
1442
_registered_sigwinch = False
 
1443
 
 
1444
def watch_sigwinch():
 
1445
    """Register for SIGWINCH, once and only once."""
 
1446
    global _registered_sigwinch
 
1447
    if not _registered_sigwinch:
 
1448
        if sys.platform == 'win32':
 
1449
            # Martin (gz) mentioned WINDOW_BUFFER_SIZE_RECORD from
 
1450
            # ReadConsoleInput but I've no idea how to plug that in
 
1451
            # the current design -- vila 20091216
1313
1452
            pass
1314
 
    if width <= 0:
1315
 
        width = 80
1316
 
 
1317
 
    return width
 
1453
        else:
 
1454
            signal.signal(signal.SIGWINCH, _terminal_size_changed)
 
1455
        _registered_sigwinch = True
1318
1456
 
1319
1457
 
1320
1458
def supports_executable():
1945
2083
    anything goes wrong.
1946
2084
    """
1947
2085
    global _cached_local_concurrency
 
2086
 
1948
2087
    if _cached_local_concurrency is not None and use_cache:
1949
2088
        return _cached_local_concurrency
1950
2089
 
1951
 
    try:
1952
 
        concurrency = _local_concurrency()
1953
 
    except (OSError, IOError):
1954
 
        concurrency = None
 
2090
    concurrency = os.environ.get('BZR_CONCURRENCY', None)
 
2091
    if concurrency is None:
 
2092
        try:
 
2093
            concurrency = _local_concurrency()
 
2094
        except (OSError, IOError):
 
2095
            pass
1955
2096
    try:
1956
2097
        concurrency = int(concurrency)
1957
2098
    except (TypeError, ValueError):
1959
2100
    if use_cache:
1960
2101
        _cached_concurrency = concurrency
1961
2102
    return concurrency
 
2103
 
 
2104
 
 
2105
class UnicodeOrBytesToBytesWriter(codecs.StreamWriter):
 
2106
    """A stream writer that doesn't decode str arguments."""
 
2107
 
 
2108
    def __init__(self, encode, stream, errors='strict'):
 
2109
        codecs.StreamWriter.__init__(self, stream, errors)
 
2110
        self.encode = encode
 
2111
 
 
2112
    def write(self, object):
 
2113
        if type(object) is str:
 
2114
            self.stream.write(object)
 
2115
        else:
 
2116
            data, _ = self.encode(object, self.errors)
 
2117
            self.stream.write(data)