/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/osutils.py

  • Committer: Aaron Bentley
  • Date: 2007-02-06 14:52:16 UTC
  • mfrom: (2266 +trunk)
  • mto: This revision was merged to the branch mainline in revision 2268.
  • Revision ID: abentley@panoramicfeedback.com-20070206145216-fcpi8o3ufvuzwbp9
Merge bzr.dev

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Bazaar -- distributed version control
2
 
#
3
 
# Copyright (C) 2005 by Canonical Ltd
 
1
# Copyright (C) 2005, 2006 Canonical Ltd
4
2
#
5
3
# This program is free software; you can redistribute it and/or modify
6
4
# it under the terms of the GNU General Public License as published by
17
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18
16
 
19
17
from cStringIO import StringIO
 
18
import os
 
19
import re
 
20
import stat
 
21
from stat import (S_ISREG, S_ISDIR, S_ISLNK, ST_MODE, ST_SIZE,
 
22
                  S_ISCHR, S_ISBLK, S_ISFIFO, S_ISSOCK)
 
23
import sys
 
24
import time
 
25
 
 
26
from bzrlib.lazy_import import lazy_import
 
27
lazy_import(globals(), """
 
28
import codecs
 
29
from datetime import datetime
20
30
import errno
21
31
from ntpath import (abspath as _nt_abspath,
22
32
                    join as _nt_join,
24
34
                    realpath as _nt_realpath,
25
35
                    splitdrive as _nt_splitdrive,
26
36
                    )
27
 
import os
28
 
from os import listdir
29
37
import posixpath
30
 
import re
31
38
import sha
32
39
import shutil
33
 
from shutil import copyfile
34
 
import stat
35
 
from stat import (S_ISREG, S_ISDIR, S_ISLNK, ST_MODE, ST_SIZE,
36
 
                  S_ISCHR, S_ISBLK, S_ISFIFO, S_ISSOCK)
37
 
import string
38
 
import sys
39
 
import time
40
 
import types
 
40
from shutil import (
 
41
    rmtree,
 
42
    )
41
43
import tempfile
 
44
from tempfile import (
 
45
    mkdtemp,
 
46
    )
42
47
import unicodedata
43
48
 
 
49
from bzrlib import (
 
50
    errors,
 
51
    win32utils,
 
52
    )
 
53
""")
 
54
 
44
55
import bzrlib
45
 
from bzrlib.errors import (BzrError,
46
 
                           BzrBadParameterNotUnicode,
47
 
                           NoSuchFile,
48
 
                           PathNotChild,
49
 
                           IllegalPath,
50
 
                           )
51
 
from bzrlib.symbol_versioning import (deprecated_function, 
52
 
        zero_nine)
 
56
from bzrlib.symbol_versioning import (
 
57
    deprecated_function,
 
58
    zero_nine,
 
59
    )
53
60
from bzrlib.trace import mutter
54
61
 
55
62
 
122
129
        return _mapper(_lstat(f).st_mode)
123
130
    except OSError, e:
124
131
        if getattr(e, 'errno', None) == errno.ENOENT:
125
 
            raise bzrlib.errors.NoSuchFile(f)
 
132
            raise errors.NoSuchFile(f)
126
133
        raise
127
134
 
128
135
 
144
151
    elif kind == 'symlink':
145
152
        return '@'
146
153
    else:
147
 
        raise BzrError('invalid file kind %r' % kind)
 
154
        raise errors.BzrError('invalid file kind %r' % kind)
148
155
 
149
156
lexists = getattr(os.path, 'lexists', None)
150
157
if lexists is None:
159
166
            if e.errno == errno.ENOENT:
160
167
                return False;
161
168
            else:
162
 
                raise BzrError("lstat/stat of (%r): %r" % (f, e))
 
169
                raise errors.BzrError("lstat/stat of (%r): %r" % (f, e))
163
170
 
164
171
 
165
172
def fancy_rename(old, new, rename_func, unlink_func):
186
193
    file_existed = False
187
194
    try:
188
195
        rename_func(new, tmp_name)
189
 
    except (NoSuchFile,), e:
 
196
    except (errors.NoSuchFile,), e:
190
197
        pass
191
198
    except IOError, e:
192
199
        # RBC 20060103 abstraction leakage: the paramiko SFTP clients rename
221
228
# choke on a Unicode string containing a relative path if
222
229
# os.getcwd() returns a non-sys.getdefaultencoding()-encoded
223
230
# string.
224
 
_fs_enc = sys.getfilesystemencoding()
 
231
_fs_enc = sys.getfilesystemencoding() or 'utf-8'
225
232
def _posix_abspath(path):
226
233
    # jam 20060426 rather than encoding to fsencoding
227
234
    # copy posixpath.abspath, but use os.getcwdu instead
302
309
pathjoin = os.path.join
303
310
normpath = os.path.normpath
304
311
getcwd = os.getcwdu
305
 
mkdtemp = tempfile.mkdtemp
306
312
rename = os.rename
307
313
dirname = os.path.dirname
308
314
basename = os.path.basename
309
 
rmtree = shutil.rmtree
 
315
split = os.path.split
 
316
splitext = os.path.splitext
 
317
# These were already imported into local scope
 
318
# mkdtemp = tempfile.mkdtemp
 
319
# rmtree = shutil.rmtree
310
320
 
311
321
MIN_ABS_PATHLENGTH = 1
312
322
 
326
336
        """Error handler for shutil.rmtree function [for win32]
327
337
        Helps to remove files and dirs marked as read-only.
328
338
        """
329
 
        type_, value = excinfo[:2]
 
339
        exception = excinfo[1]
330
340
        if function in (os.remove, os.rmdir) \
331
 
            and type_ == OSError \
332
 
            and value.errno == errno.EACCES:
333
 
            bzrlib.osutils.make_writable(path)
 
341
            and isinstance(exception, OSError) \
 
342
            and exception.errno == errno.EACCES:
 
343
            make_writable(path)
334
344
            function(path)
335
345
        else:
336
346
            raise
366
376
            mutter('encoding stdout as sys.stdin encoding %r', output_encoding)
367
377
    else:
368
378
        mutter('encoding stdout as sys.stdout encoding %r', output_encoding)
 
379
    if output_encoding == 'cp0':
 
380
        # invalid encoding (cp0 means 'no codepage' on Windows)
 
381
        output_encoding = bzrlib.user_encoding
 
382
        mutter('cp0 is invalid encoding.'
 
383
               ' encoding stdout as bzrlib.user_encoding %r', output_encoding)
 
384
    # check encoding
 
385
    try:
 
386
        codecs.lookup(output_encoding)
 
387
    except LookupError:
 
388
        sys.stderr.write('bzr: warning:'
 
389
                         ' unknown terminal encoding %s.\n'
 
390
                         '  Using encoding %s instead.\n'
 
391
                         % (output_encoding, bzrlib.user_encoding)
 
392
                        )
 
393
        output_encoding = bzrlib.user_encoding
 
394
 
369
395
    return output_encoding
370
396
 
371
397
 
440
466
    
441
467
    The empty string as a dir name is taken as top-of-tree and matches 
442
468
    everything.
443
 
    
444
 
    >>> is_inside('src', pathjoin('src', 'foo.c'))
445
 
    True
446
 
    >>> is_inside('src', 'srccontrol')
447
 
    False
448
 
    >>> is_inside('src', pathjoin('src', 'a', 'a', 'a', 'foo.c'))
449
 
    True
450
 
    >>> is_inside('foo.c', 'foo.c')
451
 
    True
452
 
    >>> is_inside('foo.c', '')
453
 
    False
454
 
    >>> is_inside('', 'foo.c')
455
 
    True
456
469
    """
457
470
    # XXX: Most callers of this can actually do something smarter by 
458
471
    # looking at the inventory
554
567
 
555
568
def local_time_offset(t=None):
556
569
    """Return offset of local zone from GMT, either at present or at time t."""
557
 
    # python2.3 localtime() can't take None
558
570
    if t is None:
559
571
        t = time.time()
560
 
        
561
 
    if time.localtime(t).tm_isdst and time.daylight:
562
 
        return -time.altzone
563
 
    else:
564
 
        return -time.timezone
 
572
    offset = datetime.fromtimestamp(t) - datetime.utcfromtimestamp(t)
 
573
    return offset.days * 86400 + offset.seconds
565
574
 
566
575
    
567
576
def format_date(t, offset=0, timezone='original', date_fmt=None, 
581
590
        tt = time.localtime(t)
582
591
        offset = local_time_offset(t)
583
592
    else:
584
 
        raise BzrError("unsupported timezone format %r" % timezone,
585
 
                       ['options are "utc", "original", "local"'])
 
593
        raise errors.BzrError("unsupported timezone format %r" % timezone,
 
594
                              ['options are "utc", "original", "local"'])
586
595
    if date_fmt is None:
587
596
        date_fmt = "%a %Y-%m-%d %H:%M:%S"
588
597
    if show_offset:
596
605
    return time.strftime('%Y%m%d%H%M%S', time.gmtime(when))
597
606
    
598
607
 
 
608
def format_delta(delta):
 
609
    """Get a nice looking string for a time delta.
 
610
 
 
611
    :param delta: The time difference in seconds, can be positive or negative.
 
612
        positive indicates time in the past, negative indicates time in the
 
613
        future. (usually time.time() - stored_time)
 
614
    :return: String formatted to show approximate resolution
 
615
    """
 
616
    delta = int(delta)
 
617
    if delta >= 0:
 
618
        direction = 'ago'
 
619
    else:
 
620
        direction = 'in the future'
 
621
        delta = -delta
 
622
 
 
623
    seconds = delta
 
624
    if seconds < 90: # print seconds up to 90 seconds
 
625
        if seconds == 1:
 
626
            return '%d second %s' % (seconds, direction,)
 
627
        else:
 
628
            return '%d seconds %s' % (seconds, direction)
 
629
 
 
630
    minutes = int(seconds / 60)
 
631
    seconds -= 60 * minutes
 
632
    if seconds == 1:
 
633
        plural_seconds = ''
 
634
    else:
 
635
        plural_seconds = 's'
 
636
    if minutes < 90: # print minutes, seconds up to 90 minutes
 
637
        if minutes == 1:
 
638
            return '%d minute, %d second%s %s' % (
 
639
                    minutes, seconds, plural_seconds, direction)
 
640
        else:
 
641
            return '%d minutes, %d second%s %s' % (
 
642
                    minutes, seconds, plural_seconds, direction)
 
643
 
 
644
    hours = int(minutes / 60)
 
645
    minutes -= 60 * hours
 
646
    if minutes == 1:
 
647
        plural_minutes = ''
 
648
    else:
 
649
        plural_minutes = 's'
 
650
 
 
651
    if hours == 1:
 
652
        return '%d hour, %d minute%s %s' % (hours, minutes,
 
653
                                            plural_minutes, direction)
 
654
    return '%d hours, %d minute%s %s' % (hours, minutes,
 
655
                                         plural_minutes, direction)
599
656
 
600
657
def filesize(f):
601
658
    """Return size of given open file."""
611
668
except (NotImplementedError, AttributeError):
612
669
    # If python doesn't have os.urandom, or it doesn't work,
613
670
    # then try to first pull random data from /dev/urandom
614
 
    if os.path.exists("/dev/urandom"):
 
671
    try:
615
672
        rand_bytes = file('/dev/urandom', 'rb').read
616
673
    # Otherwise, use this hack as a last resort
617
 
    else:
 
674
    except (IOError, OSError):
618
675
        # not well seeded, but better than nothing
619
676
        def rand_bytes(n):
620
677
            import random
642
699
## decomposition (might be too tricksy though.)
643
700
 
644
701
def splitpath(p):
645
 
    """Turn string into list of parts.
646
 
 
647
 
    >>> splitpath('a')
648
 
    ['a']
649
 
    >>> splitpath('a/b')
650
 
    ['a', 'b']
651
 
    >>> splitpath('a/./b')
652
 
    ['a', 'b']
653
 
    >>> splitpath('a/.b')
654
 
    ['a', '.b']
655
 
    >>> splitpath('a/../b')
656
 
    Traceback (most recent call last):
657
 
    ...
658
 
    BzrError: sorry, '..' not allowed in path
659
 
    """
660
 
    assert isinstance(p, types.StringTypes)
 
702
    """Turn string into list of parts."""
 
703
    assert isinstance(p, basestring)
661
704
 
662
705
    # split on either delimiter because people might use either on
663
706
    # Windows
666
709
    rps = []
667
710
    for f in ps:
668
711
        if f == '..':
669
 
            raise BzrError("sorry, %r not allowed in path" % f)
 
712
            raise errors.BzrError("sorry, %r not allowed in path" % f)
670
713
        elif (f == '.') or (f == ''):
671
714
            pass
672
715
        else:
677
720
    assert isinstance(p, list)
678
721
    for f in p:
679
722
        if (f == '..') or (f is None) or (f == ''):
680
 
            raise BzrError("sorry, %r not allowed in path" % f)
 
723
            raise errors.BzrError("sorry, %r not allowed in path" % f)
681
724
    return pathjoin(*p)
682
725
 
683
726
 
705
748
def link_or_copy(src, dest):
706
749
    """Hardlink a file, or copy it if it can't be hardlinked."""
707
750
    if not hardlinks_good():
708
 
        copyfile(src, dest)
 
751
        shutil.copyfile(src, dest)
709
752
        return
710
753
    try:
711
754
        os.link(src, dest)
712
755
    except (OSError, IOError), e:
713
756
        if e.errno != errno.EXDEV:
714
757
            raise
715
 
        copyfile(src, dest)
 
758
        shutil.copyfile(src, dest)
716
759
 
717
760
def delete_any(full_path):
718
761
    """Delete a file or directory."""
734
777
 
735
778
def contains_whitespace(s):
736
779
    """True if there are any whitespace characters in s."""
737
 
    for ch in string.whitespace:
 
780
    # string.whitespace can include '\xa0' in certain locales, because it is
 
781
    # considered "non-breaking-space" as part of ISO-8859-1. But it
 
782
    # 1) Isn't a breaking whitespace
 
783
    # 2) Isn't one of ' \t\r\n' which are characters we sometimes use as
 
784
    #    separators
 
785
    # 3) '\xa0' isn't unicode safe since it is >128.
 
786
    # So we are following textwrap's example and hard-coding our own.
 
787
    # We probably could ignore \v and \f, too.
 
788
    for ch in u' \t\n\r\v\f':
738
789
        if ch in s:
739
790
            return True
740
791
    else:
776
827
        if tail:
777
828
            s.insert(0, tail)
778
829
    else:
779
 
        raise PathNotChild(rp, base)
 
830
        raise errors.PathNotChild(rp, base)
780
831
 
781
832
    if s:
782
833
        return pathjoin(*s)
797
848
    try:
798
849
        return unicode_or_utf8_string.decode('utf8')
799
850
    except UnicodeDecodeError:
800
 
        raise BzrBadParameterNotUnicode(unicode_or_utf8_string)
 
851
        raise errors.BzrBadParameterNotUnicode(unicode_or_utf8_string)
801
852
 
802
853
 
803
854
_platform_normalizes_filenames = False
847
898
def terminal_width():
848
899
    """Return estimated terminal width."""
849
900
    if sys.platform == 'win32':
850
 
        import bzrlib.win32console
851
 
        return bzrlib.win32console.get_console_size()[0]
 
901
        return win32utils.get_console_size()[0]
852
902
    width = 0
853
903
    try:
854
904
        import struct, fcntl, termios
872
922
    return sys.platform != "win32"
873
923
 
874
924
 
 
925
def supports_posix_readonly():
 
926
    """Return True if 'readonly' has POSIX semantics, False otherwise.
 
927
 
 
928
    Notably, a win32 readonly file cannot be deleted, unlike POSIX where the
 
929
    directory controls creation/deletion, etc.
 
930
 
 
931
    And under win32, readonly means that the directory itself cannot be
 
932
    deleted.  The contents of a readonly directory can be changed, unlike POSIX
 
933
    where files in readonly directories cannot be added, deleted or renamed.
 
934
    """
 
935
    return sys.platform != "win32"
 
936
 
 
937
 
875
938
def set_or_unset_env(env_variable, value):
876
939
    """Modify the environment, setting or removing the env_variable.
877
940
 
902
965
    if sys.platform != "win32":
903
966
        return
904
967
    if _validWin32PathRE.match(path) is None:
905
 
        raise IllegalPath(path)
 
968
        raise errors.IllegalPath(path)
906
969
 
907
970
 
908
971
def walkdirs(top, prefix=""):
941
1004
    lstat = os.lstat
942
1005
    pending = []
943
1006
    _directory = _directory_kind
944
 
    _listdir = listdir
 
1007
    _listdir = os.listdir
945
1008
    pending = [(prefix, "", _directory, None, top)]
946
1009
    while pending:
947
1010
        dirblock = []
1025
1088
_cached_user_encoding = None
1026
1089
 
1027
1090
 
1028
 
def get_user_encoding():
 
1091
def get_user_encoding(use_cache=True):
1029
1092
    """Find out what the preferred user encoding is.
1030
1093
 
1031
1094
    This is generally the encoding that is used for command line parameters
1032
1095
    and file contents. This may be different from the terminal encoding
1033
1096
    or the filesystem encoding.
1034
1097
 
 
1098
    :param  use_cache:  Enable cache for detected encoding.
 
1099
                        (This parameter is turned on by default,
 
1100
                        and required only for selftesting)
 
1101
 
1035
1102
    :return: A string defining the preferred user encoding
1036
1103
    """
1037
1104
    global _cached_user_encoding
1038
 
    if _cached_user_encoding is not None:
 
1105
    if _cached_user_encoding is not None and use_cache:
1039
1106
        return _cached_user_encoding
1040
1107
 
1041
1108
    if sys.platform == 'darwin':
1049
1116
        import locale
1050
1117
 
1051
1118
    try:
1052
 
        _cached_user_encoding = locale.getpreferredencoding()
 
1119
        user_encoding = locale.getpreferredencoding()
1053
1120
    except locale.Error, e:
1054
1121
        sys.stderr.write('bzr: warning: %s\n'
1055
1122
                         '  Could not determine what text encoding to use.\n'
1057
1124
                         '  doesn\'t support the locale set by $LANG (%s)\n'
1058
1125
                         "  Continuing with ascii encoding.\n"
1059
1126
                         % (e, os.environ.get('LANG')))
1060
 
 
1061
 
    if _cached_user_encoding is None:
1062
 
        _cached_user_encoding = 'ascii'
1063
 
    return _cached_user_encoding
 
1127
        user_encoding = 'ascii'
 
1128
 
 
1129
    # Windows returns 'cp0' to indicate there is no code page. So we'll just
 
1130
    # treat that as ASCII, and not support printing unicode characters to the
 
1131
    # console.
 
1132
    if user_encoding in (None, 'cp0'):
 
1133
        user_encoding = 'ascii'
 
1134
    else:
 
1135
        # check encoding
 
1136
        try:
 
1137
            codecs.lookup(user_encoding)
 
1138
        except LookupError:
 
1139
            sys.stderr.write('bzr: warning:'
 
1140
                             ' unknown encoding %s.'
 
1141
                             ' Continuing with ascii encoding.\n'
 
1142
                             % user_encoding
 
1143
                            )
 
1144
            user_encoding = 'ascii'
 
1145
 
 
1146
    if use_cache:
 
1147
        _cached_user_encoding = user_encoding
 
1148
 
 
1149
    return user_encoding
 
1150
 
 
1151
 
 
1152
def recv_all(socket, bytes):
 
1153
    """Receive an exact number of bytes.
 
1154
 
 
1155
    Regular Socket.recv() may return less than the requested number of bytes,
 
1156
    dependning on what's in the OS buffer.  MSG_WAITALL is not available
 
1157
    on all platforms, but this should work everywhere.  This will return
 
1158
    less than the requested amount if the remote end closes.
 
1159
 
 
1160
    This isn't optimized and is intended mostly for use in testing.
 
1161
    """
 
1162
    b = ''
 
1163
    while len(b) < bytes:
 
1164
        new = socket.recv(bytes - len(b))
 
1165
        if new == '':
 
1166
            break # eof
 
1167
        b += new
 
1168
    return b
 
1169
 
 
1170
def dereference_path(path):
 
1171
    """Determine the real path to a file.
 
1172
 
 
1173
    All parent elements are dereferenced.  But the file itself is not
 
1174
    dereferenced.
 
1175
    :param path: The original path.  May be absolute or relative.
 
1176
    :return: the real path *to* the file
 
1177
    """
 
1178
    parent, base = os.path.split(path)
 
1179
    # The pathjoin for '.' is a workaround for Python bug #1213894.
 
1180
    # (initial path components aren't dereferenced)
 
1181
    return pathjoin(realpath(pathjoin('.', parent)), base)