~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/osutils.py

  • Committer: Gordon Tyler
  • Date: 2010-01-14 15:24:04 UTC
  • mto: (5037.3.1 integration)
  • mto: This revision was merged to the branch mainline in revision 5046.
  • Revision ID: gordon@doxxx.net-20100114152404-d64ik2afl96lcml0
Reverted changes to test_rules since the original should work now.

Show diffs side-by-side

added added

removed removed

Lines of Context:
21
21
                  S_ISCHR, S_ISBLK, S_ISFIFO, S_ISSOCK)
22
22
import sys
23
23
import time
 
24
import warnings
24
25
 
25
26
from bzrlib.lazy_import import lazy_import
26
27
lazy_import(globals(), """
38
39
from shutil import (
39
40
    rmtree,
40
41
    )
 
42
import signal
41
43
import subprocess
42
44
import tempfile
43
45
from tempfile import (
70
72
from bzrlib import symbol_versioning
71
73
 
72
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
 
73
84
# On win32, O_BINARY is used to indicate the file should
74
85
# be opened in binary mode, rather than text mode.
75
86
# On other platforms, O_BINARY doesn't exist, because
223
234
    else:
224
235
        file_existed = True
225
236
 
 
237
    failure_exc = None
226
238
    success = False
227
239
    try:
228
240
        try:
234
246
            # source and target may be aliases of each other (e.g. on a
235
247
            # case-insensitive filesystem), so we may have accidentally renamed
236
248
            # source by when we tried to rename target
237
 
            if not (file_existed and e.errno in (None, errno.ENOENT)):
238
 
                raise
 
249
            failure_exc = sys.exc_info()
 
250
            if (file_existed and e.errno in (None, errno.ENOENT)
 
251
                and old.lower() == new.lower()):
 
252
                # source and target are the same file on a case-insensitive
 
253
                # filesystem, so we don't generate an exception
 
254
                failure_exc = None
239
255
    finally:
240
256
        if file_existed:
241
257
            # If the file used to exist, rename it back into place
244
260
                unlink_func(tmp_name)
245
261
            else:
246
262
                rename_func(tmp_name, new)
 
263
    if failure_exc is not None:
 
264
        raise failure_exc[0], failure_exc[1], failure_exc[2]
247
265
 
248
266
 
249
267
# In Python 2.4.2 and older, os.path.abspath and os.path.realpath
687
705
    return offset.days * 86400 + offset.seconds
688
706
 
689
707
weekdays = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
 
708
_default_format_by_weekday_num = [wd + " %Y-%m-%d %H:%M:%S" for wd in weekdays]
 
709
 
690
710
 
691
711
def format_date(t, offset=0, timezone='original', date_fmt=None,
692
712
                show_offset=True):
706
726
    date_str = time.strftime(date_fmt, tt)
707
727
    return date_str + offset_str
708
728
 
 
729
 
 
730
# Cache of formatted offset strings
 
731
_offset_cache = {}
 
732
 
 
733
 
 
734
def format_date_with_offset_in_original_timezone(t, offset=0,
 
735
    _cache=_offset_cache):
 
736
    """Return a formatted date string in the original timezone.
 
737
 
 
738
    This routine may be faster then format_date.
 
739
 
 
740
    :param t: Seconds since the epoch.
 
741
    :param offset: Timezone offset in seconds east of utc.
 
742
    """
 
743
    if offset is None:
 
744
        offset = 0
 
745
    tt = time.gmtime(t + offset)
 
746
    date_fmt = _default_format_by_weekday_num[tt[6]]
 
747
    date_str = time.strftime(date_fmt, tt)
 
748
    offset_str = _cache.get(offset, None)
 
749
    if offset_str is None:
 
750
        offset_str = ' %+03d%02d' % (offset / 3600, (offset / 60) % 60)
 
751
        _cache[offset] = offset_str
 
752
    return date_str + offset_str
 
753
 
 
754
 
709
755
def format_local_date(t, offset=0, timezone='original', date_fmt=None,
710
756
                      show_offset=True):
711
757
    """Return an unicode date string formatted according to the current locale.
725
771
        date_str = date_str.decode(get_user_encoding(), 'replace')
726
772
    return date_str + offset_str
727
773
 
 
774
 
728
775
def _format_date(t, offset, timezone, date_fmt, show_offset):
729
776
    if timezone == 'utc':
730
777
        tt = time.gmtime(t)
881
928
    return parents
882
929
 
883
930
 
 
931
_extension_load_failures = []
 
932
 
 
933
 
 
934
def failed_to_load_extension(exception):
 
935
    """Handle failing to load a binary extension.
 
936
 
 
937
    This should be called from the ImportError block guarding the attempt to
 
938
    import the native extension.  If this function returns, the pure-Python
 
939
    implementation should be loaded instead::
 
940
 
 
941
    >>> try:
 
942
    >>>     import bzrlib._fictional_extension_pyx
 
943
    >>> except ImportError, e:
 
944
    >>>     bzrlib.osutils.failed_to_load_extension(e)
 
945
    >>>     import bzrlib._fictional_extension_py
 
946
    """
 
947
    # NB: This docstring is just an example, not a doctest, because doctest
 
948
    # currently can't cope with the use of lazy imports in this namespace --
 
949
    # mbp 20090729
 
950
    
 
951
    # This currently doesn't report the failure at the time it occurs, because
 
952
    # they tend to happen very early in startup when we can't check config
 
953
    # files etc, and also we want to report all failures but not spam the user
 
954
    # with 10 warnings.
 
955
    from bzrlib import trace
 
956
    exception_str = str(exception)
 
957
    if exception_str not in _extension_load_failures:
 
958
        trace.mutter("failed to load compiled extension: %s" % exception_str)
 
959
        _extension_load_failures.append(exception_str)
 
960
 
 
961
 
 
962
def report_extension_load_failures():
 
963
    if not _extension_load_failures:
 
964
        return
 
965
    from bzrlib.config import GlobalConfig
 
966
    if GlobalConfig().get_user_option_as_bool('ignore_missing_extensions'):
 
967
        return
 
968
    # the warnings framework should by default show this only once
 
969
    from bzrlib.trace import warning
 
970
    warning(
 
971
        "bzr: warning: some compiled extensions could not be loaded; "
 
972
        "see <https://answers.launchpad.net/bzr/+faq/703>")
 
973
    # we no longer show the specific missing extensions here, because it makes
 
974
    # the message too long and scary - see
 
975
    # https://bugs.launchpad.net/bzr/+bug/430529
 
976
 
 
977
 
884
978
try:
885
979
    from bzrlib._chunks_to_lines_pyx import chunks_to_lines
886
 
except ImportError:
 
980
except ImportError, e:
 
981
    failed_to_load_extension(e)
887
982
    from bzrlib._chunks_to_lines_py import chunks_to_lines
888
983
 
889
984
 
1083
1178
    bit_iter = iter(rel.split('/'))
1084
1179
    for bit in bit_iter:
1085
1180
        lbit = bit.lower()
1086
 
        for look in _listdir(current):
 
1181
        try:
 
1182
            next_entries = _listdir(current)
 
1183
        except OSError: # enoent, eperm, etc
 
1184
            # We can't find this in the filesystem, so just append the
 
1185
            # remaining bits.
 
1186
            current = pathjoin(current, bit, *list(bit_iter))
 
1187
            break
 
1188
        for look in next_entries:
1087
1189
            if lbit == look.lower():
1088
1190
                current = pathjoin(current, look)
1089
1191
                break
1093
1195
            # the target of a move, for example).
1094
1196
            current = pathjoin(current, bit, *list(bit_iter))
1095
1197
            break
1096
 
    return current[len(abs_base)+1:]
 
1198
    return current[len(abs_base):].lstrip('/')
1097
1199
 
1098
1200
# XXX - TODO - we need better detection/integration of case-insensitive
1099
1201
# file-systems; Linux often sees FAT32 devices (or NFS-mounted OSX
1238
1340
    normalized_filename = _inaccessible_normalized_filename
1239
1341
 
1240
1342
 
 
1343
default_terminal_width = 80
 
1344
"""The default terminal width for ttys.
 
1345
 
 
1346
This is defined so that higher levels can share a common fallback value when
 
1347
terminal_width() returns None.
 
1348
"""
 
1349
 
 
1350
 
1241
1351
def terminal_width():
1242
 
    """Return estimated terminal width."""
1243
 
    if sys.platform == 'win32':
1244
 
        return win32utils.get_console_size()[0]
1245
 
    width = 0
 
1352
    """Return terminal width.
 
1353
 
 
1354
    None is returned if the width can't established precisely.
 
1355
 
 
1356
    The rules are:
 
1357
    - if BZR_COLUMNS is set, returns its value
 
1358
    - if there is no controlling terminal, returns None
 
1359
    - if COLUMNS is set, returns its value,
 
1360
 
 
1361
    From there, we need to query the OS to get the size of the controlling
 
1362
    terminal.
 
1363
 
 
1364
    Unices:
 
1365
    - get termios.TIOCGWINSZ
 
1366
    - if an error occurs or a negative value is obtained, returns None
 
1367
 
 
1368
    Windows:
 
1369
    
 
1370
    - win32utils.get_console_size() decides,
 
1371
    - returns None on error (provided default value)
 
1372
    """
 
1373
 
 
1374
    # If BZR_COLUMNS is set, take it, user is always right
 
1375
    try:
 
1376
        return int(os.environ['BZR_COLUMNS'])
 
1377
    except (KeyError, ValueError):
 
1378
        pass
 
1379
 
 
1380
    isatty = getattr(sys.stdout, 'isatty', None)
 
1381
    if  isatty is None or not isatty():
 
1382
        # Don't guess, setting BZR_COLUMNS is the recommended way to override.
 
1383
        return None
 
1384
 
 
1385
    # If COLUMNS is set, take it, the terminal knows better (even inside a
 
1386
    # given terminal, the application can decide to set COLUMNS to a lower
 
1387
    # value (splitted screen) or a bigger value (scroll bars))
 
1388
    try:
 
1389
        return int(os.environ['COLUMNS'])
 
1390
    except (KeyError, ValueError):
 
1391
        pass
 
1392
 
 
1393
    width, height = _terminal_size(None, None)
 
1394
    if width <= 0:
 
1395
        # Consider invalid values as meaning no width
 
1396
        return None
 
1397
 
 
1398
    return width
 
1399
 
 
1400
 
 
1401
def _win32_terminal_size(width, height):
 
1402
    width, height = win32utils.get_console_size(defaultx=width, defaulty=height)
 
1403
    return width, height
 
1404
 
 
1405
 
 
1406
def _ioctl_terminal_size(width, height):
1246
1407
    try:
1247
1408
        import struct, fcntl, termios
1248
1409
        s = struct.pack('HHHH', 0, 0, 0, 0)
1249
1410
        x = fcntl.ioctl(1, termios.TIOCGWINSZ, s)
1250
 
        width = struct.unpack('HHHH', x)[1]
1251
 
    except IOError:
 
1411
        height, width = struct.unpack('HHHH', x)[0:2]
 
1412
    except (IOError, AttributeError):
1252
1413
        pass
1253
 
    if width <= 0:
1254
 
        try:
1255
 
            width = int(os.environ['COLUMNS'])
1256
 
        except:
1257
 
            pass
1258
 
    if width <= 0:
1259
 
        width = 80
1260
 
 
1261
 
    return width
 
1414
    return width, height
 
1415
 
 
1416
_terminal_size = None
 
1417
"""Returns the terminal size as (width, height).
 
1418
 
 
1419
:param width: Default value for width.
 
1420
:param height: Default value for height.
 
1421
 
 
1422
This is defined specifically for each OS and query the size of the controlling
 
1423
terminal. If any error occurs, the provided default values should be returned.
 
1424
"""
 
1425
if sys.platform == 'win32':
 
1426
    _terminal_size = _win32_terminal_size
 
1427
else:
 
1428
    _terminal_size = _ioctl_terminal_size
 
1429
 
 
1430
 
 
1431
def _terminal_size_changed(signum, frame):
 
1432
    """Set COLUMNS upon receiving a SIGnal for WINdow size CHange."""
 
1433
    width, height = _terminal_size(None, None)
 
1434
    if width is not None:
 
1435
        os.environ['COLUMNS'] = str(width)
 
1436
 
 
1437
if sys.platform == 'win32':
 
1438
    # Martin (gz) mentioned WINDOW_BUFFER_SIZE_RECORD from ReadConsoleInput but
 
1439
    # I've no idea how to plug that in the current design -- vila 20091216
 
1440
    pass
 
1441
else:
 
1442
    signal.signal(signal.SIGWINCH, _terminal_size_changed)
1262
1443
 
1263
1444
 
1264
1445
def supports_executable():
1466
1647
            try:
1467
1648
                from bzrlib._readdir_pyx import UTF8DirReader
1468
1649
                _selected_dir_reader = UTF8DirReader()
1469
 
            except ImportError:
 
1650
            except ImportError, e:
 
1651
                failed_to_load_extension(e)
1470
1652
                pass
1471
1653
 
1472
1654
    if _selected_dir_reader is None:
1778
1960
        try:
1779
1961
            from bzrlib._readdir_pyx import UTF8DirReader
1780
1962
            file_kind_from_stat_mode = UTF8DirReader().kind_from_mode
1781
 
        except ImportError:
 
1963
        except ImportError, e:
 
1964
            # This is one time where we won't warn that an extension failed to
 
1965
            # load. The extension is never available on Windows anyway.
1782
1966
            from bzrlib._readdir_py import (
1783
1967
                _kind_from_mode as file_kind_from_stat_mode
1784
1968
                )
1886
2070
    anything goes wrong.
1887
2071
    """
1888
2072
    global _cached_local_concurrency
 
2073
 
1889
2074
    if _cached_local_concurrency is not None and use_cache:
1890
2075
        return _cached_local_concurrency
1891
2076
 
1892
 
    try:
1893
 
        concurrency = _local_concurrency()
1894
 
    except (OSError, IOError):
1895
 
        concurrency = None
 
2077
    concurrency = os.environ.get('BZR_CONCURRENCY', None)
 
2078
    if concurrency is None:
 
2079
        try:
 
2080
            concurrency = _local_concurrency()
 
2081
        except (OSError, IOError):
 
2082
            pass
1896
2083
    try:
1897
2084
        concurrency = int(concurrency)
1898
2085
    except (TypeError, ValueError):