~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/osutils.py

  • Committer: John Arbash Meinel
  • Date: 2006-10-11 00:23:23 UTC
  • mfrom: (2070 +trunk)
  • mto: This revision was merged to the branch mainline in revision 2071.
  • Revision ID: john@arbash-meinel.com-20061011002323-82ba88c293d7caff
[merge] bzr.dev 2070

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Bazaar-NG -- distributed version control
 
1
# Bazaar -- distributed version control
2
2
#
3
3
# Copyright (C) 2005 by Canonical Ltd
4
4
#
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
 
 
63
# On win32, O_BINARY is used to indicate the file should
 
64
# be opened in binary mode, rather than text mode.
 
65
# On other platforms, O_BINARY doesn't exist, because
 
66
# they always open in binary mode, so it is okay to
 
67
# OR with 0 on those platforms
 
68
O_BINARY = getattr(os, 'O_BINARY', 0)
 
69
 
 
70
 
56
71
def make_readonly(filename):
57
72
    """Make a filename read-only."""
58
73
    mod = os.stat(filename).st_mode
76
91
    Windows."""
77
92
    # TODO: I'm not really sure this is the best format either.x
78
93
    global _QUOTE_RE
79
 
    if _QUOTE_RE == None:
 
94
    if _QUOTE_RE is None:
80
95
        _QUOTE_RE = re.compile(r'([^a-zA-Z0-9.,:/\\_~-])')
81
96
        
82
97
    if _QUOTE_RE.search(f):
114
129
        return _mapper(_lstat(f).st_mode)
115
130
    except OSError, e:
116
131
        if getattr(e, 'errno', None) == errno.ENOENT:
117
 
            raise bzrlib.errors.NoSuchFile(f)
 
132
            raise errors.NoSuchFile(f)
118
133
        raise
119
134
 
120
135
 
 
136
def get_umask():
 
137
    """Return the current umask"""
 
138
    # Assume that people aren't messing with the umask while running
 
139
    # XXX: This is not thread safe, but there is no way to get the
 
140
    #      umask without setting it
 
141
    umask = os.umask(0)
 
142
    os.umask(umask)
 
143
    return umask
 
144
 
 
145
 
121
146
def kind_marker(kind):
122
147
    if kind == 'file':
123
148
        return ''
126
151
    elif kind == 'symlink':
127
152
        return '@'
128
153
    else:
129
 
        raise BzrError('invalid file kind %r' % kind)
 
154
        raise errors.BzrError('invalid file kind %r' % kind)
130
155
 
131
156
lexists = getattr(os.path, 'lexists', None)
132
157
if lexists is None:
133
158
    def lexists(f):
134
159
        try:
135
 
            if hasattr(os, 'lstat'):
 
160
            if getattr(os, 'lstat') is not None:
136
161
                os.lstat(f)
137
162
            else:
138
163
                os.stat(f)
141
166
            if e.errno == errno.ENOENT:
142
167
                return False;
143
168
            else:
144
 
                raise BzrError("lstat/stat of (%r): %r" % (f, e))
 
169
                raise errors.BzrError("lstat/stat of (%r): %r" % (f, e))
145
170
 
146
171
 
147
172
def fancy_rename(old, new, rename_func, unlink_func):
168
193
    file_existed = False
169
194
    try:
170
195
        rename_func(new, tmp_name)
171
 
    except (NoSuchFile,), e:
 
196
    except (errors.NoSuchFile,), e:
172
197
        pass
173
198
    except IOError, e:
174
199
        # RBC 20060103 abstraction leakage: the paramiko SFTP clients rename
175
 
        # function raises an IOError with errno == None when a rename fails.
 
200
        # function raises an IOError with errno is None when a rename fails.
176
201
        # This then gets caught here.
177
202
        if e.errno not in (None, errno.ENOENT, errno.ENOTDIR):
178
203
            raise
179
204
    except Exception, e:
180
 
        if (not hasattr(e, 'errno') 
 
205
        if (getattr(e, 'errno', None) is None
181
206
            or e.errno not in (errno.ENOENT, errno.ENOTDIR)):
182
207
            raise
183
208
    else:
284
309
pathjoin = os.path.join
285
310
normpath = os.path.normpath
286
311
getcwd = os.getcwdu
287
 
mkdtemp = tempfile.mkdtemp
288
312
rename = os.rename
289
313
dirname = os.path.dirname
290
314
basename = os.path.basename
291
 
rmtree = shutil.rmtree
 
315
# These were already imported into local scope
 
316
# mkdtemp = tempfile.mkdtemp
 
317
# rmtree = shutil.rmtree
292
318
 
293
319
MIN_ABS_PATHLENGTH = 1
294
320
 
312
338
        if function in (os.remove, os.rmdir) \
313
339
            and type_ == OSError \
314
340
            and value.errno == errno.EACCES:
315
 
            bzrlib.osutils.make_writable(path)
 
341
            make_writable(path)
316
342
            function(path)
317
343
        else:
318
344
            raise
352
378
 
353
379
 
354
380
def normalizepath(f):
355
 
    if hasattr(os.path, 'realpath'):
 
381
    if getattr(os.path, 'realpath', None) is not None:
356
382
        F = realpath
357
383
    else:
358
384
        F = abspath
422
448
    
423
449
    The empty string as a dir name is taken as top-of-tree and matches 
424
450
    everything.
425
 
    
426
 
    >>> is_inside('src', pathjoin('src', 'foo.c'))
427
 
    True
428
 
    >>> is_inside('src', 'srccontrol')
429
 
    False
430
 
    >>> is_inside('src', pathjoin('src', 'a', 'a', 'a', 'foo.c'))
431
 
    True
432
 
    >>> is_inside('foo.c', 'foo.c')
433
 
    True
434
 
    >>> is_inside('foo.c', '')
435
 
    False
436
 
    >>> is_inside('', 'foo.c')
437
 
    True
438
451
    """
439
452
    # XXX: Most callers of this can actually do something smarter by 
440
453
    # looking at the inventory
487
500
 
488
501
 
489
502
def sha_file(f):
490
 
    if hasattr(f, 'tell'):
 
503
    if getattr(f, 'tell', None) is not None:
491
504
        assert f.tell() == 0
492
505
    s = sha.new()
493
506
    BUFSIZE = 128<<10
537
550
def local_time_offset(t=None):
538
551
    """Return offset of local zone from GMT, either at present or at time t."""
539
552
    # python2.3 localtime() can't take None
540
 
    if t == None:
 
553
    if t is None:
541
554
        t = time.time()
542
555
        
543
556
    if time.localtime(t).tm_isdst and time.daylight:
556
569
        tt = time.gmtime(t)
557
570
        offset = 0
558
571
    elif timezone == 'original':
559
 
        if offset == None:
 
572
        if offset is None:
560
573
            offset = 0
561
574
        tt = time.gmtime(t + offset)
562
575
    elif timezone == 'local':
563
576
        tt = time.localtime(t)
564
577
        offset = local_time_offset(t)
565
578
    else:
566
 
        raise BzrError("unsupported timezone format %r" % timezone,
567
 
                       ['options are "utc", "original", "local"'])
 
579
        raise errors.BzrError("unsupported timezone format %r" % timezone,
 
580
                              ['options are "utc", "original", "local"'])
568
581
    if date_fmt is None:
569
582
        date_fmt = "%a %Y-%m-%d %H:%M:%S"
570
583
    if show_offset:
578
591
    return time.strftime('%Y%m%d%H%M%S', time.gmtime(when))
579
592
    
580
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)
581
642
 
582
643
def filesize(f):
583
644
    """Return size of given open file."""
593
654
except (NotImplementedError, AttributeError):
594
655
    # If python doesn't have os.urandom, or it doesn't work,
595
656
    # then try to first pull random data from /dev/urandom
596
 
    if os.path.exists("/dev/urandom"):
 
657
    try:
597
658
        rand_bytes = file('/dev/urandom', 'rb').read
598
659
    # Otherwise, use this hack as a last resort
599
 
    else:
 
660
    except (IOError, OSError):
600
661
        # not well seeded, but better than nothing
601
662
        def rand_bytes(n):
602
663
            import random
624
685
## decomposition (might be too tricksy though.)
625
686
 
626
687
def splitpath(p):
627
 
    """Turn string into list of parts.
628
 
 
629
 
    >>> splitpath('a')
630
 
    ['a']
631
 
    >>> splitpath('a/b')
632
 
    ['a', 'b']
633
 
    >>> splitpath('a/./b')
634
 
    ['a', 'b']
635
 
    >>> splitpath('a/.b')
636
 
    ['a', '.b']
637
 
    >>> splitpath('a/../b')
638
 
    Traceback (most recent call last):
639
 
    ...
640
 
    BzrError: sorry, '..' not allowed in path
641
 
    """
642
 
    assert isinstance(p, types.StringTypes)
 
688
    """Turn string into list of parts."""
 
689
    assert isinstance(p, basestring)
643
690
 
644
691
    # split on either delimiter because people might use either on
645
692
    # Windows
648
695
    rps = []
649
696
    for f in ps:
650
697
        if f == '..':
651
 
            raise BzrError("sorry, %r not allowed in path" % f)
 
698
            raise errors.BzrError("sorry, %r not allowed in path" % f)
652
699
        elif (f == '.') or (f == ''):
653
700
            pass
654
701
        else:
658
705
def joinpath(p):
659
706
    assert isinstance(p, list)
660
707
    for f in p:
661
 
        if (f == '..') or (f == None) or (f == ''):
662
 
            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)
663
710
    return pathjoin(*p)
664
711
 
665
712
 
687
734
def link_or_copy(src, dest):
688
735
    """Hardlink a file, or copy it if it can't be hardlinked."""
689
736
    if not hardlinks_good():
690
 
        copyfile(src, dest)
 
737
        shutil.copyfile(src, dest)
691
738
        return
692
739
    try:
693
740
        os.link(src, dest)
694
741
    except (OSError, IOError), e:
695
742
        if e.errno != errno.EXDEV:
696
743
            raise
697
 
        copyfile(src, dest)
 
744
        shutil.copyfile(src, dest)
698
745
 
699
746
def delete_any(full_path):
700
747
    """Delete a file or directory."""
708
755
 
709
756
 
710
757
def has_symlinks():
711
 
    if hasattr(os, 'symlink'):
 
758
    if getattr(os, 'symlink', None) is not None:
712
759
        return True
713
760
    else:
714
761
        return False
758
805
        if tail:
759
806
            s.insert(0, tail)
760
807
    else:
761
 
        raise PathNotChild(rp, base)
 
808
        raise errors.PathNotChild(rp, base)
762
809
 
763
810
    if s:
764
811
        return pathjoin(*s)
779
826
    try:
780
827
        return unicode_or_utf8_string.decode('utf8')
781
828
    except UnicodeDecodeError:
782
 
        raise BzrBadParameterNotUnicode(unicode_or_utf8_string)
 
829
        raise errors.BzrBadParameterNotUnicode(unicode_or_utf8_string)
783
830
 
784
831
 
785
832
_platform_normalizes_filenames = False
849
896
 
850
897
    return width
851
898
 
 
899
 
852
900
def supports_executable():
853
901
    return sys.platform != "win32"
854
902
 
855
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
 
856
923
_validWin32PathRE = re.compile(r'^([A-Za-z]:[/\\])?[^:<>*"?\|]*$')
857
924
 
858
925
 
864
931
    if sys.platform != "win32":
865
932
        return
866
933
    if _validWin32PathRE.match(path) is None:
867
 
        raise IllegalPath(path)
 
934
        raise errors.IllegalPath(path)
868
935
 
869
936
 
870
937
def walkdirs(top, prefix=""):
875
942
    to exclude some directories, they are then not descended into.
876
943
    
877
944
    The data yielded is of the form:
878
 
    [(relpath, basename, kind, lstat, path_from_top), ...]
 
945
    ((directory-relpath, directory-path-from-top),
 
946
    [(relpath, basename, kind, lstat), ...]),
 
947
     - directory-relpath is the relative path of the directory being returned
 
948
       with respect to top. prefix is prepended to this.
 
949
     - directory-path-from-root is the path including top for this directory. 
 
950
       It is suitable for use with os functions.
 
951
     - relpath is the relative path within the subtree being walked.
 
952
     - basename is the basename of the path
 
953
     - kind is the kind of the file now. If unknown then the file is not
 
954
       present within the tree - but it may be recorded as versioned. See
 
955
       versioned_kind.
 
956
     - lstat is the stat data *if* the file was statted.
 
957
     - planned, not implemented: 
 
958
       path_from_tree_root is the path from the root of the tree.
879
959
 
880
960
    :param prefix: Prefix the relpaths that are yielded with 'prefix'. This 
881
961
        allows one to walk a subtree but get paths that are relative to a tree
882
962
        rooted higher up.
883
963
    :return: an iterator over the dirs.
884
964
    """
 
965
    #TODO there is a bit of a smell where the results of the directory-
 
966
    # summary in this, and the path from the root, may not agree 
 
967
    # depending on top and prefix - i.e. ./foo and foo as a pair leads to
 
968
    # potentially confusing output. We should make this more robust - but
 
969
    # not at a speed cost. RBC 20060731
885
970
    lstat = os.lstat
886
971
    pending = []
887
972
    _directory = _directory_kind
888
 
    _listdir = listdir
 
973
    _listdir = os.listdir
889
974
    pending = [(prefix, "", _directory, None, top)]
890
975
    while pending:
891
976
        dirblock = []
899
984
        for name in sorted(_listdir(top)):
900
985
            abspath = top + '/' + name
901
986
            statvalue = lstat(abspath)
902
 
            dirblock.append ((relroot + name, name, file_kind_from_stat_mode(statvalue.st_mode), statvalue, abspath))
903
 
        yield dirblock
 
987
            dirblock.append((relroot + name, name,
 
988
                file_kind_from_stat_mode(statvalue.st_mode),
 
989
                statvalue, abspath))
 
990
        yield (currentdir[0], top), dirblock
904
991
        # push the user specified dirs from dirblock
905
992
        for dir in reversed(dirblock):
906
993
            if dir[2] == _directory:
907
994
                pending.append(dir)
908
995
 
909
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
 
910
1039
def path_prefix_key(path):
911
1040
    """Generate a prefix-order path key for path.
912
1041
 
920
1049
    key_a = path_prefix_key(path_a)
921
1050
    key_b = path_prefix_key(path_b)
922
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