~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/osutils.py

  • Committer: wang
  • Date: 2006-10-29 13:41:32 UTC
  • mto: (2104.4.1 wang_65714)
  • mto: This revision was merged to the branch mainline in revision 2109.
  • Revision ID: wang@ubuntu-20061029134132-3d7f4216f20c4aef
Replace python's difflib by patiencediff because the worst case 
performance is cubic for difflib and people commiting large data 
files are often hurt by this. The worst case performance of patience is 
quadratic. Fix bug 65714.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
# Bazaar -- distributed version control
2
2
#
3
 
# Copyright (C) 2005 by Canonical Ltd
 
3
# Copyright (C) 2005 Canonical Ltd
4
4
#
5
5
# This program is free software; you can redistribute it and/or modify
6
6
# it under the terms of the GNU General Public License as published by
17
17
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18
18
 
19
19
from cStringIO import StringIO
 
20
import os
 
21
import re
 
22
import stat
 
23
from stat import (S_ISREG, S_ISDIR, S_ISLNK, ST_MODE, ST_SIZE,
 
24
                  S_ISCHR, S_ISBLK, S_ISFIFO, S_ISSOCK)
 
25
import sys
 
26
import time
 
27
 
 
28
from bzrlib.lazy_import import lazy_import
 
29
lazy_import(globals(), """
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)
 
40
from shutil import (
 
41
    rmtree,
 
42
    )
37
43
import string
38
 
import sys
39
 
import time
40
 
import types
41
44
import tempfile
 
45
from tempfile import (
 
46
    mkdtemp,
 
47
    )
42
48
import unicodedata
43
49
 
 
50
from bzrlib import (
 
51
    errors,
 
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
 
84
91
    Windows."""
85
92
    # TODO: I'm not really sure this is the best format either.x
86
93
    global _QUOTE_RE
87
 
    if _QUOTE_RE == None:
 
94
    if _QUOTE_RE is None:
88
95
        _QUOTE_RE = re.compile(r'([^a-zA-Z0-9.,:/\\_~-])')
89
96
        
90
97
    if _QUOTE_RE.search(f):
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:
151
158
    def lexists(f):
152
159
        try:
153
 
            if hasattr(os, 'lstat'):
 
160
            if getattr(os, 'lstat') is not None:
154
161
                os.lstat(f)
155
162
            else:
156
163
                os.stat(f)
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
193
 
        # function raises an IOError with errno == None when a rename fails.
 
200
        # function raises an IOError with errno is None when a rename fails.
194
201
        # This then gets caught here.
195
202
        if e.errno not in (None, errno.ENOENT, errno.ENOTDIR):
196
203
            raise
197
204
    except Exception, e:
198
 
        if (not hasattr(e, 'errno') 
 
205
        if (getattr(e, 'errno', None) is None
199
206
            or e.errno not in (errno.ENOENT, errno.ENOTDIR)):
200
207
            raise
201
208
    else:
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
# These were already imported into local scope
 
316
# mkdtemp = tempfile.mkdtemp
 
317
# rmtree = shutil.rmtree
310
318
 
311
319
MIN_ABS_PATHLENGTH = 1
312
320
 
330
338
        if function in (os.remove, os.rmdir) \
331
339
            and type_ == OSError \
332
340
            and value.errno == errno.EACCES:
333
 
            bzrlib.osutils.make_writable(path)
 
341
            make_writable(path)
334
342
            function(path)
335
343
        else:
336
344
            raise
370
378
 
371
379
 
372
380
def normalizepath(f):
373
 
    if hasattr(os.path, 'realpath'):
 
381
    if getattr(os.path, 'realpath', None) is not None:
374
382
        F = realpath
375
383
    else:
376
384
        F = abspath
440
448
    
441
449
    The empty string as a dir name is taken as top-of-tree and matches 
442
450
    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
451
    """
457
452
    # XXX: Most callers of this can actually do something smarter by 
458
453
    # looking at the inventory
505
500
 
506
501
 
507
502
def sha_file(f):
508
 
    if hasattr(f, 'tell'):
 
503
    if getattr(f, 'tell', None) is not None:
509
504
        assert f.tell() == 0
510
505
    s = sha.new()
511
506
    BUFSIZE = 128<<10
555
550
def local_time_offset(t=None):
556
551
    """Return offset of local zone from GMT, either at present or at time t."""
557
552
    # python2.3 localtime() can't take None
558
 
    if t == None:
 
553
    if t is None:
559
554
        t = time.time()
560
555
        
561
556
    if time.localtime(t).tm_isdst and time.daylight:
574
569
        tt = time.gmtime(t)
575
570
        offset = 0
576
571
    elif timezone == 'original':
577
 
        if offset == None:
 
572
        if offset is None:
578
573
            offset = 0
579
574
        tt = time.gmtime(t + offset)
580
575
    elif timezone == 'local':
581
576
        tt = time.localtime(t)
582
577
        offset = local_time_offset(t)
583
578
    else:
584
 
        raise BzrError("unsupported timezone format %r" % timezone,
585
 
                       ['options are "utc", "original", "local"'])
 
579
        raise errors.BzrError("unsupported timezone format %r" % timezone,
 
580
                              ['options are "utc", "original", "local"'])
586
581
    if date_fmt is None:
587
582
        date_fmt = "%a %Y-%m-%d %H:%M:%S"
588
583
    if show_offset:
596
591
    return time.strftime('%Y%m%d%H%M%S', time.gmtime(when))
597
592
    
598
593
 
 
594
def format_delta(delta):
 
595
    """Get a nice looking string for a time delta.
 
596
 
 
597
    :param delta: The time difference in seconds, can be positive or negative.
 
598
        positive indicates time in the past, negative indicates time in the
 
599
        future. (usually time.time() - stored_time)
 
600
    :return: String formatted to show approximate resolution
 
601
    """
 
602
    delta = int(delta)
 
603
    if delta >= 0:
 
604
        direction = 'ago'
 
605
    else:
 
606
        direction = 'in the future'
 
607
        delta = -delta
 
608
 
 
609
    seconds = delta
 
610
    if seconds < 90: # print seconds up to 90 seconds
 
611
        if seconds == 1:
 
612
            return '%d second %s' % (seconds, direction,)
 
613
        else:
 
614
            return '%d seconds %s' % (seconds, direction)
 
615
 
 
616
    minutes = int(seconds / 60)
 
617
    seconds -= 60 * minutes
 
618
    if seconds == 1:
 
619
        plural_seconds = ''
 
620
    else:
 
621
        plural_seconds = 's'
 
622
    if minutes < 90: # print minutes, seconds up to 90 minutes
 
623
        if minutes == 1:
 
624
            return '%d minute, %d second%s %s' % (
 
625
                    minutes, seconds, plural_seconds, direction)
 
626
        else:
 
627
            return '%d minutes, %d second%s %s' % (
 
628
                    minutes, seconds, plural_seconds, direction)
 
629
 
 
630
    hours = int(minutes / 60)
 
631
    minutes -= 60 * hours
 
632
    if minutes == 1:
 
633
        plural_minutes = ''
 
634
    else:
 
635
        plural_minutes = 's'
 
636
 
 
637
    if hours == 1:
 
638
        return '%d hour, %d minute%s %s' % (hours, minutes,
 
639
                                            plural_minutes, direction)
 
640
    return '%d hours, %d minute%s %s' % (hours, minutes,
 
641
                                         plural_minutes, direction)
599
642
 
600
643
def filesize(f):
601
644
    """Return size of given open file."""
611
654
except (NotImplementedError, AttributeError):
612
655
    # If python doesn't have os.urandom, or it doesn't work,
613
656
    # then try to first pull random data from /dev/urandom
614
 
    if os.path.exists("/dev/urandom"):
 
657
    try:
615
658
        rand_bytes = file('/dev/urandom', 'rb').read
616
659
    # Otherwise, use this hack as a last resort
617
 
    else:
 
660
    except (IOError, OSError):
618
661
        # not well seeded, but better than nothing
619
662
        def rand_bytes(n):
620
663
            import random
642
685
## decomposition (might be too tricksy though.)
643
686
 
644
687
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)
 
688
    """Turn string into list of parts."""
 
689
    assert isinstance(p, basestring)
661
690
 
662
691
    # split on either delimiter because people might use either on
663
692
    # Windows
666
695
    rps = []
667
696
    for f in ps:
668
697
        if f == '..':
669
 
            raise BzrError("sorry, %r not allowed in path" % f)
 
698
            raise errors.BzrError("sorry, %r not allowed in path" % f)
670
699
        elif (f == '.') or (f == ''):
671
700
            pass
672
701
        else:
676
705
def joinpath(p):
677
706
    assert isinstance(p, list)
678
707
    for f in p:
679
 
        if (f == '..') or (f == None) or (f == ''):
680
 
            raise BzrError("sorry, %r not allowed in path" % f)
 
708
        if (f == '..') or (f is None) or (f == ''):
 
709
            raise errors.BzrError("sorry, %r not allowed in path" % f)
681
710
    return pathjoin(*p)
682
711
 
683
712
 
705
734
def link_or_copy(src, dest):
706
735
    """Hardlink a file, or copy it if it can't be hardlinked."""
707
736
    if not hardlinks_good():
708
 
        copyfile(src, dest)
 
737
        shutil.copyfile(src, dest)
709
738
        return
710
739
    try:
711
740
        os.link(src, dest)
712
741
    except (OSError, IOError), e:
713
742
        if e.errno != errno.EXDEV:
714
743
            raise
715
 
        copyfile(src, dest)
 
744
        shutil.copyfile(src, dest)
716
745
 
717
746
def delete_any(full_path):
718
747
    """Delete a file or directory."""
726
755
 
727
756
 
728
757
def has_symlinks():
729
 
    if hasattr(os, 'symlink'):
 
758
    if getattr(os, 'symlink', None) is not None:
730
759
        return True
731
760
    else:
732
761
        return False
776
805
        if tail:
777
806
            s.insert(0, tail)
778
807
    else:
779
 
        raise PathNotChild(rp, base)
 
808
        raise errors.PathNotChild(rp, base)
780
809
 
781
810
    if s:
782
811
        return pathjoin(*s)
797
826
    try:
798
827
        return unicode_or_utf8_string.decode('utf8')
799
828
    except UnicodeDecodeError:
800
 
        raise BzrBadParameterNotUnicode(unicode_or_utf8_string)
 
829
        raise errors.BzrBadParameterNotUnicode(unicode_or_utf8_string)
801
830
 
802
831
 
803
832
_platform_normalizes_filenames = False
867
896
 
868
897
    return width
869
898
 
 
899
 
870
900
def supports_executable():
871
901
    return sys.platform != "win32"
872
902
 
873
903
 
 
904
def set_or_unset_env(env_variable, value):
 
905
    """Modify the environment, setting or removing the env_variable.
 
906
 
 
907
    :param env_variable: The environment variable in question
 
908
    :param value: The value to set the environment to. If None, then
 
909
        the variable will be removed.
 
910
    :return: The original value of the environment variable.
 
911
    """
 
912
    orig_val = os.environ.get(env_variable)
 
913
    if value is None:
 
914
        if orig_val is not None:
 
915
            del os.environ[env_variable]
 
916
    else:
 
917
        if isinstance(value, unicode):
 
918
            value = value.encode(bzrlib.user_encoding)
 
919
        os.environ[env_variable] = value
 
920
    return orig_val
 
921
 
 
922
 
874
923
_validWin32PathRE = re.compile(r'^([A-Za-z]:[/\\])?[^:<>*"?\|]*$')
875
924
 
876
925
 
882
931
    if sys.platform != "win32":
883
932
        return
884
933
    if _validWin32PathRE.match(path) is None:
885
 
        raise IllegalPath(path)
 
934
        raise errors.IllegalPath(path)
886
935
 
887
936
 
888
937
def walkdirs(top, prefix=""):
921
970
    lstat = os.lstat
922
971
    pending = []
923
972
    _directory = _directory_kind
924
 
    _listdir = listdir
 
973
    _listdir = os.listdir
925
974
    pending = [(prefix, "", _directory, None, top)]
926
975
    while pending:
927
976
        dirblock = []
945
994
                pending.append(dir)
946
995
 
947
996
 
 
997
def copy_tree(from_path, to_path, handlers={}):
 
998
    """Copy all of the entries in from_path into to_path.
 
999
 
 
1000
    :param from_path: The base directory to copy. 
 
1001
    :param to_path: The target directory. If it does not exist, it will
 
1002
        be created.
 
1003
    :param handlers: A dictionary of functions, which takes a source and
 
1004
        destinations for files, directories, etc.
 
1005
        It is keyed on the file kind, such as 'directory', 'symlink', or 'file'
 
1006
        'file', 'directory', and 'symlink' should always exist.
 
1007
        If they are missing, they will be replaced with 'os.mkdir()',
 
1008
        'os.readlink() + os.symlink()', and 'shutil.copy2()', respectively.
 
1009
    """
 
1010
    # Now, just copy the existing cached tree to the new location
 
1011
    # We use a cheap trick here.
 
1012
    # Absolute paths are prefixed with the first parameter
 
1013
    # relative paths are prefixed with the second.
 
1014
    # So we can get both the source and target returned
 
1015
    # without any extra work.
 
1016
 
 
1017
    def copy_dir(source, dest):
 
1018
        os.mkdir(dest)
 
1019
 
 
1020
    def copy_link(source, dest):
 
1021
        """Copy the contents of a symlink"""
 
1022
        link_to = os.readlink(source)
 
1023
        os.symlink(link_to, dest)
 
1024
 
 
1025
    real_handlers = {'file':shutil.copy2,
 
1026
                     'symlink':copy_link,
 
1027
                     'directory':copy_dir,
 
1028
                    }
 
1029
    real_handlers.update(handlers)
 
1030
 
 
1031
    if not os.path.exists(to_path):
 
1032
        real_handlers['directory'](from_path, to_path)
 
1033
 
 
1034
    for dir_info, entries in walkdirs(from_path, prefix=to_path):
 
1035
        for relpath, name, kind, st, abspath in entries:
 
1036
            real_handlers[kind](abspath, relpath)
 
1037
 
 
1038
 
948
1039
def path_prefix_key(path):
949
1040
    """Generate a prefix-order path key for path.
950
1041
 
958
1049
    key_a = path_prefix_key(path_a)
959
1050
    key_b = path_prefix_key(path_b)
960
1051
    return cmp(key_a, key_b)
 
1052
 
 
1053
 
 
1054
_cached_user_encoding = None
 
1055
 
 
1056
 
 
1057
def get_user_encoding():
 
1058
    """Find out what the preferred user encoding is.
 
1059
 
 
1060
    This is generally the encoding that is used for command line parameters
 
1061
    and file contents. This may be different from the terminal encoding
 
1062
    or the filesystem encoding.
 
1063
 
 
1064
    :return: A string defining the preferred user encoding
 
1065
    """
 
1066
    global _cached_user_encoding
 
1067
    if _cached_user_encoding is not None:
 
1068
        return _cached_user_encoding
 
1069
 
 
1070
    if sys.platform == 'darwin':
 
1071
        # work around egregious python 2.4 bug
 
1072
        sys.platform = 'posix'
 
1073
        try:
 
1074
            import locale
 
1075
        finally:
 
1076
            sys.platform = 'darwin'
 
1077
    else:
 
1078
        import locale
 
1079
 
 
1080
    try:
 
1081
        _cached_user_encoding = locale.getpreferredencoding()
 
1082
    except locale.Error, e:
 
1083
        sys.stderr.write('bzr: warning: %s\n'
 
1084
                         '  Could not determine what text encoding to use.\n'
 
1085
                         '  This error usually means your Python interpreter\n'
 
1086
                         '  doesn\'t support the locale set by $LANG (%s)\n'
 
1087
                         "  Continuing with ascii encoding.\n"
 
1088
                         % (e, os.environ.get('LANG')))
 
1089
 
 
1090
    if _cached_user_encoding is None:
 
1091
        _cached_user_encoding = 'ascii'
 
1092
    return _cached_user_encoding
 
1093
 
 
1094
 
 
1095
def recv_all(socket, bytes):
 
1096
    """Receive an exact number of bytes.
 
1097
 
 
1098
    Regular Socket.recv() may return less than the requested number of bytes,
 
1099
    dependning on what's in the OS buffer.  MSG_WAITALL is not available
 
1100
    on all platforms, but this should work everywhere.  This will return
 
1101
    less than the requested amount if the remote end closes.
 
1102
 
 
1103
    This isn't optimized and is intended mostly for use in testing.
 
1104
    """
 
1105
    b = ''
 
1106
    while len(b) < bytes:
 
1107
        new = socket.recv(bytes - len(b))
 
1108
        if new == '':
 
1109
            break # eof
 
1110
        b += new
 
1111
    return b
 
1112
 
 
1113
def dereference_path(path):
 
1114
    """Determine the real path to a file.
 
1115
 
 
1116
    All parent elements are dereferenced.  But the file itself is not
 
1117
    dereferenced.
 
1118
    :param path: The original path.  May be absolute or relative.
 
1119
    :return: the real path *to* the file
 
1120
    """
 
1121
    parent, base = os.path.split(path)
 
1122
    # The pathjoin for '.' is a workaround for Python bug #1213894.
 
1123
    # (initial path components aren't dereferenced)
 
1124
    return pathjoin(realpath(pathjoin('.', parent)), base)