~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/osutils.py

  • Committer: Aaron Bentley
  • Date: 2006-11-17 04:06:03 UTC
  • mfrom: (2139 +trunk)
  • mto: This revision was merged to the branch mainline in revision 2162.
  • Revision ID: aaron.bentley@utoronto.ca-20061117040603-pgebxndswvwk26tt
Merge from 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(), """
20
28
import errno
21
29
from ntpath import (abspath as _nt_abspath,
22
30
                    join as _nt_join,
24
32
                    realpath as _nt_realpath,
25
33
                    splitdrive as _nt_splitdrive,
26
34
                    )
27
 
import os
28
 
from os import listdir
29
35
import posixpath
30
 
import re
31
36
import sha
32
37
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)
 
38
from shutil import (
 
39
    rmtree,
 
40
    )
37
41
import string
38
 
import sys
39
 
import time
40
 
import types
41
42
import tempfile
 
43
from tempfile import (
 
44
    mkdtemp,
 
45
    )
42
46
import unicodedata
43
47
 
 
48
from bzrlib import (
 
49
    errors,
 
50
    )
 
51
""")
 
52
 
44
53
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)
 
54
from bzrlib.symbol_versioning import (
 
55
    deprecated_function,
 
56
    zero_nine,
 
57
    )
53
58
from bzrlib.trace import mutter
54
59
 
55
60
 
122
127
        return _mapper(_lstat(f).st_mode)
123
128
    except OSError, e:
124
129
        if getattr(e, 'errno', None) == errno.ENOENT:
125
 
            raise bzrlib.errors.NoSuchFile(f)
 
130
            raise errors.NoSuchFile(f)
126
131
        raise
127
132
 
128
133
 
144
149
    elif kind == 'symlink':
145
150
        return '@'
146
151
    else:
147
 
        raise BzrError('invalid file kind %r' % kind)
 
152
        raise errors.BzrError('invalid file kind %r' % kind)
148
153
 
149
154
lexists = getattr(os.path, 'lexists', None)
150
155
if lexists is None:
159
164
            if e.errno == errno.ENOENT:
160
165
                return False;
161
166
            else:
162
 
                raise BzrError("lstat/stat of (%r): %r" % (f, e))
 
167
                raise errors.BzrError("lstat/stat of (%r): %r" % (f, e))
163
168
 
164
169
 
165
170
def fancy_rename(old, new, rename_func, unlink_func):
186
191
    file_existed = False
187
192
    try:
188
193
        rename_func(new, tmp_name)
189
 
    except (NoSuchFile,), e:
 
194
    except (errors.NoSuchFile,), e:
190
195
        pass
191
196
    except IOError, e:
192
197
        # RBC 20060103 abstraction leakage: the paramiko SFTP clients rename
221
226
# choke on a Unicode string containing a relative path if
222
227
# os.getcwd() returns a non-sys.getdefaultencoding()-encoded
223
228
# string.
224
 
_fs_enc = sys.getfilesystemencoding()
 
229
_fs_enc = sys.getfilesystemencoding() or 'utf-8'
225
230
def _posix_abspath(path):
226
231
    # jam 20060426 rather than encoding to fsencoding
227
232
    # copy posixpath.abspath, but use os.getcwdu instead
302
307
pathjoin = os.path.join
303
308
normpath = os.path.normpath
304
309
getcwd = os.getcwdu
305
 
mkdtemp = tempfile.mkdtemp
306
310
rename = os.rename
307
311
dirname = os.path.dirname
308
312
basename = os.path.basename
309
 
rmtree = shutil.rmtree
 
313
# These were already imported into local scope
 
314
# mkdtemp = tempfile.mkdtemp
 
315
# rmtree = shutil.rmtree
310
316
 
311
317
MIN_ABS_PATHLENGTH = 1
312
318
 
326
332
        """Error handler for shutil.rmtree function [for win32]
327
333
        Helps to remove files and dirs marked as read-only.
328
334
        """
329
 
        type_, value = excinfo[:2]
 
335
        exception = excinfo[1]
330
336
        if function in (os.remove, os.rmdir) \
331
 
            and type_ == OSError \
332
 
            and value.errno == errno.EACCES:
333
 
            bzrlib.osutils.make_writable(path)
 
337
            and isinstance(exception, OSError) \
 
338
            and exception.errno == errno.EACCES:
 
339
            make_writable(path)
334
340
            function(path)
335
341
        else:
336
342
            raise
366
372
            mutter('encoding stdout as sys.stdin encoding %r', output_encoding)
367
373
    else:
368
374
        mutter('encoding stdout as sys.stdout encoding %r', output_encoding)
 
375
    if output_encoding == 'cp0':
 
376
        # invalid encoding (cp0 means 'no codepage' on Windows)
 
377
        output_encoding = bzrlib.user_encoding
 
378
        mutter('cp0 is invalid encoding.'
 
379
               ' encoding stdout as bzrlib.user_encoding %r', output_encoding)
369
380
    return output_encoding
370
381
 
371
382
 
440
451
    
441
452
    The empty string as a dir name is taken as top-of-tree and matches 
442
453
    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
454
    """
457
455
    # XXX: Most callers of this can actually do something smarter by 
458
456
    # looking at the inventory
581
579
        tt = time.localtime(t)
582
580
        offset = local_time_offset(t)
583
581
    else:
584
 
        raise BzrError("unsupported timezone format %r" % timezone,
585
 
                       ['options are "utc", "original", "local"'])
 
582
        raise errors.BzrError("unsupported timezone format %r" % timezone,
 
583
                              ['options are "utc", "original", "local"'])
586
584
    if date_fmt is None:
587
585
        date_fmt = "%a %Y-%m-%d %H:%M:%S"
588
586
    if show_offset:
596
594
    return time.strftime('%Y%m%d%H%M%S', time.gmtime(when))
597
595
    
598
596
 
 
597
def format_delta(delta):
 
598
    """Get a nice looking string for a time delta.
 
599
 
 
600
    :param delta: The time difference in seconds, can be positive or negative.
 
601
        positive indicates time in the past, negative indicates time in the
 
602
        future. (usually time.time() - stored_time)
 
603
    :return: String formatted to show approximate resolution
 
604
    """
 
605
    delta = int(delta)
 
606
    if delta >= 0:
 
607
        direction = 'ago'
 
608
    else:
 
609
        direction = 'in the future'
 
610
        delta = -delta
 
611
 
 
612
    seconds = delta
 
613
    if seconds < 90: # print seconds up to 90 seconds
 
614
        if seconds == 1:
 
615
            return '%d second %s' % (seconds, direction,)
 
616
        else:
 
617
            return '%d seconds %s' % (seconds, direction)
 
618
 
 
619
    minutes = int(seconds / 60)
 
620
    seconds -= 60 * minutes
 
621
    if seconds == 1:
 
622
        plural_seconds = ''
 
623
    else:
 
624
        plural_seconds = 's'
 
625
    if minutes < 90: # print minutes, seconds up to 90 minutes
 
626
        if minutes == 1:
 
627
            return '%d minute, %d second%s %s' % (
 
628
                    minutes, seconds, plural_seconds, direction)
 
629
        else:
 
630
            return '%d minutes, %d second%s %s' % (
 
631
                    minutes, seconds, plural_seconds, direction)
 
632
 
 
633
    hours = int(minutes / 60)
 
634
    minutes -= 60 * hours
 
635
    if minutes == 1:
 
636
        plural_minutes = ''
 
637
    else:
 
638
        plural_minutes = 's'
 
639
 
 
640
    if hours == 1:
 
641
        return '%d hour, %d minute%s %s' % (hours, minutes,
 
642
                                            plural_minutes, direction)
 
643
    return '%d hours, %d minute%s %s' % (hours, minutes,
 
644
                                         plural_minutes, direction)
599
645
 
600
646
def filesize(f):
601
647
    """Return size of given open file."""
611
657
except (NotImplementedError, AttributeError):
612
658
    # If python doesn't have os.urandom, or it doesn't work,
613
659
    # then try to first pull random data from /dev/urandom
614
 
    if os.path.exists("/dev/urandom"):
 
660
    try:
615
661
        rand_bytes = file('/dev/urandom', 'rb').read
616
662
    # Otherwise, use this hack as a last resort
617
 
    else:
 
663
    except (IOError, OSError):
618
664
        # not well seeded, but better than nothing
619
665
        def rand_bytes(n):
620
666
            import random
642
688
## decomposition (might be too tricksy though.)
643
689
 
644
690
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)
 
691
    """Turn string into list of parts."""
 
692
    assert isinstance(p, basestring)
661
693
 
662
694
    # split on either delimiter because people might use either on
663
695
    # Windows
666
698
    rps = []
667
699
    for f in ps:
668
700
        if f == '..':
669
 
            raise BzrError("sorry, %r not allowed in path" % f)
 
701
            raise errors.BzrError("sorry, %r not allowed in path" % f)
670
702
        elif (f == '.') or (f == ''):
671
703
            pass
672
704
        else:
677
709
    assert isinstance(p, list)
678
710
    for f in p:
679
711
        if (f == '..') or (f is None) or (f == ''):
680
 
            raise BzrError("sorry, %r not allowed in path" % f)
 
712
            raise errors.BzrError("sorry, %r not allowed in path" % f)
681
713
    return pathjoin(*p)
682
714
 
683
715
 
705
737
def link_or_copy(src, dest):
706
738
    """Hardlink a file, or copy it if it can't be hardlinked."""
707
739
    if not hardlinks_good():
708
 
        copyfile(src, dest)
 
740
        shutil.copyfile(src, dest)
709
741
        return
710
742
    try:
711
743
        os.link(src, dest)
712
744
    except (OSError, IOError), e:
713
745
        if e.errno != errno.EXDEV:
714
746
            raise
715
 
        copyfile(src, dest)
 
747
        shutil.copyfile(src, dest)
716
748
 
717
749
def delete_any(full_path):
718
750
    """Delete a file or directory."""
776
808
        if tail:
777
809
            s.insert(0, tail)
778
810
    else:
779
 
        raise PathNotChild(rp, base)
 
811
        raise errors.PathNotChild(rp, base)
780
812
 
781
813
    if s:
782
814
        return pathjoin(*s)
797
829
    try:
798
830
        return unicode_or_utf8_string.decode('utf8')
799
831
    except UnicodeDecodeError:
800
 
        raise BzrBadParameterNotUnicode(unicode_or_utf8_string)
 
832
        raise errors.BzrBadParameterNotUnicode(unicode_or_utf8_string)
801
833
 
802
834
 
803
835
_platform_normalizes_filenames = False
902
934
    if sys.platform != "win32":
903
935
        return
904
936
    if _validWin32PathRE.match(path) is None:
905
 
        raise IllegalPath(path)
 
937
        raise errors.IllegalPath(path)
906
938
 
907
939
 
908
940
def walkdirs(top, prefix=""):
941
973
    lstat = os.lstat
942
974
    pending = []
943
975
    _directory = _directory_kind
944
 
    _listdir = listdir
 
976
    _listdir = os.listdir
945
977
    pending = [(prefix, "", _directory, None, top)]
946
978
    while pending:
947
979
        dirblock = []
1058
1090
                         "  Continuing with ascii encoding.\n"
1059
1091
                         % (e, os.environ.get('LANG')))
1060
1092
 
1061
 
    if _cached_user_encoding is None:
 
1093
    # Windows returns 'cp0' to indicate there is no code page. So we'll just
 
1094
    # treat that as ASCII, and not support printing unicode characters to the
 
1095
    # console.
 
1096
    if _cached_user_encoding in (None, 'cp0'):
1062
1097
        _cached_user_encoding = 'ascii'
1063
1098
    return _cached_user_encoding
 
1099
 
 
1100
 
 
1101
def recv_all(socket, bytes):
 
1102
    """Receive an exact number of bytes.
 
1103
 
 
1104
    Regular Socket.recv() may return less than the requested number of bytes,
 
1105
    dependning on what's in the OS buffer.  MSG_WAITALL is not available
 
1106
    on all platforms, but this should work everywhere.  This will return
 
1107
    less than the requested amount if the remote end closes.
 
1108
 
 
1109
    This isn't optimized and is intended mostly for use in testing.
 
1110
    """
 
1111
    b = ''
 
1112
    while len(b) < bytes:
 
1113
        new = socket.recv(bytes - len(b))
 
1114
        if new == '':
 
1115
            break # eof
 
1116
        b += new
 
1117
    return b
 
1118
 
 
1119
def dereference_path(path):
 
1120
    """Determine the real path to a file.
 
1121
 
 
1122
    All parent elements are dereferenced.  But the file itself is not
 
1123
    dereferenced.
 
1124
    :param path: The original path.  May be absolute or relative.
 
1125
    :return: the real path *to* the file
 
1126
    """
 
1127
    parent, base = os.path.split(path)
 
1128
    # The pathjoin for '.' is a workaround for Python bug #1213894.
 
1129
    # (initial path components aren't dereferenced)
 
1130
    return pathjoin(realpath(pathjoin('.', parent)), base)