/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: Vincent Ladeuil
  • Date: 2007-07-18 09:43:41 UTC
  • mto: (2778.5.1 vila)
  • mto: This revision was merged to the branch mainline in revision 2789.
  • Revision ID: v.ladeuil+lp@free.fr-20070718094341-edmgsog3el06yqow
Add performance analysis of missing.

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, 2007 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
    cache_utf8,
 
51
    errors,
 
52
    win32utils,
 
53
    )
 
54
""")
 
55
 
44
56
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)
 
57
from bzrlib import symbol_versioning
 
58
from bzrlib.symbol_versioning import (
 
59
    deprecated_function,
 
60
    zero_nine,
 
61
    )
53
62
from bzrlib.trace import mutter
54
63
 
55
64
 
60
69
# OR with 0 on those platforms
61
70
O_BINARY = getattr(os, 'O_BINARY', 0)
62
71
 
 
72
# On posix, use lstat instead of stat so that we can
 
73
# operate on broken symlinks. On Windows revert to stat.
 
74
lstat = getattr(os, 'lstat', os.stat)
63
75
 
64
76
def make_readonly(filename):
65
77
    """Make a filename read-only."""
66
 
    mod = os.stat(filename).st_mode
67
 
    mod = mod & 0777555
68
 
    os.chmod(filename, mod)
 
78
    mod = lstat(filename).st_mode
 
79
    if not stat.S_ISLNK(mod):
 
80
        mod = mod & 0777555
 
81
        os.chmod(filename, mod)
69
82
 
70
83
 
71
84
def make_writable(filename):
72
 
    mod = os.stat(filename).st_mode
73
 
    mod = mod | 0200
74
 
    os.chmod(filename, mod)
 
85
    mod = lstat(filename).st_mode
 
86
    if not stat.S_ISLNK(mod):
 
87
        mod = mod | 0200
 
88
        os.chmod(filename, mod)
75
89
 
76
90
 
77
91
_QUOTE_RE = None
122
136
        return _mapper(_lstat(f).st_mode)
123
137
    except OSError, e:
124
138
        if getattr(e, 'errno', None) == errno.ENOENT:
125
 
            raise bzrlib.errors.NoSuchFile(f)
 
139
            raise errors.NoSuchFile(f)
126
140
        raise
127
141
 
128
142
 
136
150
    return umask
137
151
 
138
152
 
 
153
_kind_marker_map = {
 
154
    "file": "",
 
155
    _directory_kind: "/",
 
156
    "symlink": "@",
 
157
    'tree-reference': '+',
 
158
}
 
159
 
 
160
 
139
161
def kind_marker(kind):
140
 
    if kind == 'file':
141
 
        return ''
142
 
    elif kind == _directory_kind:
143
 
        return '/'
144
 
    elif kind == 'symlink':
145
 
        return '@'
146
 
    else:
147
 
        raise BzrError('invalid file kind %r' % kind)
 
162
    try:
 
163
        return _kind_marker_map[kind]
 
164
    except KeyError:
 
165
        raise errors.BzrError('invalid file kind %r' % kind)
 
166
 
148
167
 
149
168
lexists = getattr(os.path, 'lexists', None)
150
169
if lexists is None:
151
170
    def lexists(f):
152
171
        try:
153
 
            if getattr(os, 'lstat') is not None:
154
 
                os.lstat(f)
155
 
            else:
156
 
                os.stat(f)
 
172
            stat = getattr(os, 'lstat', os.stat)
 
173
            stat(f)
157
174
            return True
158
 
        except OSError,e:
 
175
        except OSError, e:
159
176
            if e.errno == errno.ENOENT:
160
177
                return False;
161
178
            else:
162
 
                raise BzrError("lstat/stat of (%r): %r" % (f, e))
 
179
                raise errors.BzrError("lstat/stat of (%r): %r" % (f, e))
163
180
 
164
181
 
165
182
def fancy_rename(old, new, rename_func, unlink_func):
186
203
    file_existed = False
187
204
    try:
188
205
        rename_func(new, tmp_name)
189
 
    except (NoSuchFile,), e:
 
206
    except (errors.NoSuchFile,), e:
190
207
        pass
191
208
    except IOError, e:
192
209
        # RBC 20060103 abstraction leakage: the paramiko SFTP clients rename
221
238
# choke on a Unicode string containing a relative path if
222
239
# os.getcwd() returns a non-sys.getdefaultencoding()-encoded
223
240
# string.
224
 
_fs_enc = sys.getfilesystemencoding()
 
241
_fs_enc = sys.getfilesystemencoding() or 'utf-8'
225
242
def _posix_abspath(path):
226
243
    # jam 20060426 rather than encoding to fsencoding
227
244
    # copy posixpath.abspath, but use os.getcwdu instead
252
269
    return _win32_fixdrive(_nt_abspath(unicode(path)).replace('\\', '/'))
253
270
 
254
271
 
 
272
def _win98_abspath(path):
 
273
    """Return the absolute version of a path.
 
274
    Windows 98 safe implementation (python reimplementation
 
275
    of Win32 API function GetFullPathNameW)
 
276
    """
 
277
    # Corner cases:
 
278
    #   C:\path     => C:/path
 
279
    #   C:/path     => C:/path
 
280
    #   \\HOST\path => //HOST/path
 
281
    #   //HOST/path => //HOST/path
 
282
    #   path        => C:/cwd/path
 
283
    #   /path       => C:/path
 
284
    path = unicode(path)
 
285
    # check for absolute path
 
286
    drive = _nt_splitdrive(path)[0]
 
287
    if drive == '' and path[:2] not in('//','\\\\'):
 
288
        cwd = os.getcwdu()
 
289
        # we cannot simply os.path.join cwd and path
 
290
        # because os.path.join('C:','/path') produce '/path'
 
291
        # and this is incorrect
 
292
        if path[:1] in ('/','\\'):
 
293
            cwd = _nt_splitdrive(cwd)[0]
 
294
            path = path[1:]
 
295
        path = cwd + '\\' + path
 
296
    return _win32_fixdrive(_nt_normpath(path).replace('\\', '/'))
 
297
 
 
298
if win32utils.winver == 'Windows 98':
 
299
    _win32_abspath = _win98_abspath
 
300
 
 
301
 
255
302
def _win32_realpath(path):
256
303
    # Real _nt_realpath doesn't have a problem with a unicode cwd
257
304
    return _win32_fixdrive(_nt_realpath(unicode(path)).replace('\\', '/'))
302
349
pathjoin = os.path.join
303
350
normpath = os.path.normpath
304
351
getcwd = os.getcwdu
305
 
mkdtemp = tempfile.mkdtemp
306
352
rename = os.rename
307
353
dirname = os.path.dirname
308
354
basename = os.path.basename
309
 
rmtree = shutil.rmtree
 
355
split = os.path.split
 
356
splitext = os.path.splitext
 
357
# These were already imported into local scope
 
358
# mkdtemp = tempfile.mkdtemp
 
359
# rmtree = shutil.rmtree
310
360
 
311
361
MIN_ABS_PATHLENGTH = 1
312
362
 
326
376
        """Error handler for shutil.rmtree function [for win32]
327
377
        Helps to remove files and dirs marked as read-only.
328
378
        """
329
 
        type_, value = excinfo[:2]
 
379
        exception = excinfo[1]
330
380
        if function in (os.remove, os.rmdir) \
331
 
            and type_ == OSError \
332
 
            and value.errno == errno.EACCES:
333
 
            bzrlib.osutils.make_writable(path)
 
381
            and isinstance(exception, OSError) \
 
382
            and exception.errno == errno.EACCES:
 
383
            make_writable(path)
334
384
            function(path)
335
385
        else:
336
386
            raise
366
416
            mutter('encoding stdout as sys.stdin encoding %r', output_encoding)
367
417
    else:
368
418
        mutter('encoding stdout as sys.stdout encoding %r', output_encoding)
 
419
    if output_encoding == 'cp0':
 
420
        # invalid encoding (cp0 means 'no codepage' on Windows)
 
421
        output_encoding = bzrlib.user_encoding
 
422
        mutter('cp0 is invalid encoding.'
 
423
               ' encoding stdout as bzrlib.user_encoding %r', output_encoding)
 
424
    # check encoding
 
425
    try:
 
426
        codecs.lookup(output_encoding)
 
427
    except LookupError:
 
428
        sys.stderr.write('bzr: warning:'
 
429
                         ' unknown terminal encoding %s.\n'
 
430
                         '  Using encoding %s instead.\n'
 
431
                         % (output_encoding, bzrlib.user_encoding)
 
432
                        )
 
433
        output_encoding = bzrlib.user_encoding
 
434
 
369
435
    return output_encoding
370
436
 
371
437
 
440
506
    
441
507
    The empty string as a dir name is taken as top-of-tree and matches 
442
508
    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
509
    """
457
510
    # XXX: Most callers of this can actually do something smarter by 
458
511
    # looking at the inventory
473
526
    for dirname in dir_list:
474
527
        if is_inside(dirname, fname):
475
528
            return True
476
 
    else:
477
 
        return False
 
529
    return False
478
530
 
479
531
 
480
532
def is_inside_or_parent_of_any(dir_list, fname):
482
534
    for dirname in dir_list:
483
535
        if is_inside(dirname, fname) or is_inside(fname, dirname):
484
536
            return True
485
 
    else:
486
 
        return False
 
537
    return False
487
538
 
488
539
 
489
540
def pumpfile(fromfile, tofile):
554
605
 
555
606
def local_time_offset(t=None):
556
607
    """Return offset of local zone from GMT, either at present or at time t."""
557
 
    # python2.3 localtime() can't take None
558
608
    if t is None:
559
609
        t = time.time()
560
 
        
561
 
    if time.localtime(t).tm_isdst and time.daylight:
562
 
        return -time.altzone
563
 
    else:
564
 
        return -time.timezone
 
610
    offset = datetime.fromtimestamp(t) - datetime.utcfromtimestamp(t)
 
611
    return offset.days * 86400 + offset.seconds
565
612
 
566
613
    
567
 
def format_date(t, offset=0, timezone='original', date_fmt=None, 
 
614
def format_date(t, offset=0, timezone='original', date_fmt=None,
568
615
                show_offset=True):
569
 
    ## TODO: Perhaps a global option to use either universal or local time?
570
 
    ## Or perhaps just let people set $TZ?
571
 
    assert isinstance(t, float)
572
 
    
 
616
    """Return a formatted date string.
 
617
 
 
618
    :param t: Seconds since the epoch.
 
619
    :param offset: Timezone offset in seconds east of utc.
 
620
    :param timezone: How to display the time: 'utc', 'original' for the
 
621
         timezone specified by offset, or 'local' for the process's current
 
622
         timezone.
 
623
    :param show_offset: Whether to append the timezone.
 
624
    :param date_fmt: strftime format.
 
625
    """
573
626
    if timezone == 'utc':
574
627
        tt = time.gmtime(t)
575
628
        offset = 0
581
634
        tt = time.localtime(t)
582
635
        offset = local_time_offset(t)
583
636
    else:
584
 
        raise BzrError("unsupported timezone format %r" % timezone,
585
 
                       ['options are "utc", "original", "local"'])
 
637
        raise errors.BzrError("unsupported timezone format %r" % timezone,
 
638
                              ['options are "utc", "original", "local"'])
586
639
    if date_fmt is None:
587
640
        date_fmt = "%a %Y-%m-%d %H:%M:%S"
588
641
    if show_offset:
596
649
    return time.strftime('%Y%m%d%H%M%S', time.gmtime(when))
597
650
    
598
651
 
 
652
def format_delta(delta):
 
653
    """Get a nice looking string for a time delta.
 
654
 
 
655
    :param delta: The time difference in seconds, can be positive or negative.
 
656
        positive indicates time in the past, negative indicates time in the
 
657
        future. (usually time.time() - stored_time)
 
658
    :return: String formatted to show approximate resolution
 
659
    """
 
660
    delta = int(delta)
 
661
    if delta >= 0:
 
662
        direction = 'ago'
 
663
    else:
 
664
        direction = 'in the future'
 
665
        delta = -delta
 
666
 
 
667
    seconds = delta
 
668
    if seconds < 90: # print seconds up to 90 seconds
 
669
        if seconds == 1:
 
670
            return '%d second %s' % (seconds, direction,)
 
671
        else:
 
672
            return '%d seconds %s' % (seconds, direction)
 
673
 
 
674
    minutes = int(seconds / 60)
 
675
    seconds -= 60 * minutes
 
676
    if seconds == 1:
 
677
        plural_seconds = ''
 
678
    else:
 
679
        plural_seconds = 's'
 
680
    if minutes < 90: # print minutes, seconds up to 90 minutes
 
681
        if minutes == 1:
 
682
            return '%d minute, %d second%s %s' % (
 
683
                    minutes, seconds, plural_seconds, direction)
 
684
        else:
 
685
            return '%d minutes, %d second%s %s' % (
 
686
                    minutes, seconds, plural_seconds, direction)
 
687
 
 
688
    hours = int(minutes / 60)
 
689
    minutes -= 60 * hours
 
690
    if minutes == 1:
 
691
        plural_minutes = ''
 
692
    else:
 
693
        plural_minutes = 's'
 
694
 
 
695
    if hours == 1:
 
696
        return '%d hour, %d minute%s %s' % (hours, minutes,
 
697
                                            plural_minutes, direction)
 
698
    return '%d hours, %d minute%s %s' % (hours, minutes,
 
699
                                         plural_minutes, direction)
599
700
 
600
701
def filesize(f):
601
702
    """Return size of given open file."""
611
712
except (NotImplementedError, AttributeError):
612
713
    # If python doesn't have os.urandom, or it doesn't work,
613
714
    # then try to first pull random data from /dev/urandom
614
 
    if os.path.exists("/dev/urandom"):
 
715
    try:
615
716
        rand_bytes = file('/dev/urandom', 'rb').read
616
717
    # Otherwise, use this hack as a last resort
617
 
    else:
 
718
    except (IOError, OSError):
618
719
        # not well seeded, but better than nothing
619
720
        def rand_bytes(n):
620
721
            import random
642
743
## decomposition (might be too tricksy though.)
643
744
 
644
745
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)
 
746
    """Turn string into list of parts."""
 
747
    assert isinstance(p, basestring)
661
748
 
662
749
    # split on either delimiter because people might use either on
663
750
    # Windows
666
753
    rps = []
667
754
    for f in ps:
668
755
        if f == '..':
669
 
            raise BzrError("sorry, %r not allowed in path" % f)
 
756
            raise errors.BzrError("sorry, %r not allowed in path" % f)
670
757
        elif (f == '.') or (f == ''):
671
758
            pass
672
759
        else:
674
761
    return rps
675
762
 
676
763
def joinpath(p):
677
 
    assert isinstance(p, list)
 
764
    assert isinstance(p, (list, tuple))
678
765
    for f in p:
679
766
        if (f == '..') or (f is None) or (f == ''):
680
 
            raise BzrError("sorry, %r not allowed in path" % f)
 
767
            raise errors.BzrError("sorry, %r not allowed in path" % f)
681
768
    return pathjoin(*p)
682
769
 
683
770
 
705
792
def link_or_copy(src, dest):
706
793
    """Hardlink a file, or copy it if it can't be hardlinked."""
707
794
    if not hardlinks_good():
708
 
        copyfile(src, dest)
 
795
        shutil.copyfile(src, dest)
709
796
        return
710
797
    try:
711
798
        os.link(src, dest)
712
799
    except (OSError, IOError), e:
713
800
        if e.errno != errno.EXDEV:
714
801
            raise
715
 
        copyfile(src, dest)
 
802
        shutil.copyfile(src, dest)
716
803
 
717
804
def delete_any(full_path):
718
805
    """Delete a file or directory."""
734
821
 
735
822
def contains_whitespace(s):
736
823
    """True if there are any whitespace characters in s."""
737
 
    for ch in string.whitespace:
 
824
    # string.whitespace can include '\xa0' in certain locales, because it is
 
825
    # considered "non-breaking-space" as part of ISO-8859-1. But it
 
826
    # 1) Isn't a breaking whitespace
 
827
    # 2) Isn't one of ' \t\r\n' which are characters we sometimes use as
 
828
    #    separators
 
829
    # 3) '\xa0' isn't unicode safe since it is >128.
 
830
 
 
831
    # This should *not* be a unicode set of characters in case the source
 
832
    # string is not a Unicode string. We can auto-up-cast the characters since
 
833
    # they are ascii, but we don't want to auto-up-cast the string in case it
 
834
    # is utf-8
 
835
    for ch in ' \t\n\r\v\f':
738
836
        if ch in s:
739
837
            return True
740
838
    else:
776
874
        if tail:
777
875
            s.insert(0, tail)
778
876
    else:
779
 
        raise PathNotChild(rp, base)
 
877
        raise errors.PathNotChild(rp, base)
780
878
 
781
879
    if s:
782
880
        return pathjoin(*s)
797
895
    try:
798
896
        return unicode_or_utf8_string.decode('utf8')
799
897
    except UnicodeDecodeError:
800
 
        raise BzrBadParameterNotUnicode(unicode_or_utf8_string)
 
898
        raise errors.BzrBadParameterNotUnicode(unicode_or_utf8_string)
 
899
 
 
900
 
 
901
def safe_utf8(unicode_or_utf8_string):
 
902
    """Coerce unicode_or_utf8_string to a utf8 string.
 
903
 
 
904
    If it is a str, it is returned.
 
905
    If it is Unicode, it is encoded into a utf-8 string.
 
906
    """
 
907
    if isinstance(unicode_or_utf8_string, str):
 
908
        # TODO: jam 20070209 This is overkill, and probably has an impact on
 
909
        #       performance if we are dealing with lots of apis that want a
 
910
        #       utf-8 revision id
 
911
        try:
 
912
            # Make sure it is a valid utf-8 string
 
913
            unicode_or_utf8_string.decode('utf-8')
 
914
        except UnicodeDecodeError:
 
915
            raise errors.BzrBadParameterNotUnicode(unicode_or_utf8_string)
 
916
        return unicode_or_utf8_string
 
917
    return unicode_or_utf8_string.encode('utf-8')
 
918
 
 
919
 
 
920
_revision_id_warning = ('Unicode revision ids were deprecated in bzr 0.15.'
 
921
                        ' Revision id generators should be creating utf8'
 
922
                        ' revision ids.')
 
923
 
 
924
 
 
925
def safe_revision_id(unicode_or_utf8_string, warn=True):
 
926
    """Revision ids should now be utf8, but at one point they were unicode.
 
927
 
 
928
    :param unicode_or_utf8_string: A possibly Unicode revision_id. (can also be
 
929
        utf8 or None).
 
930
    :param warn: Functions that are sanitizing user data can set warn=False
 
931
    :return: None or a utf8 revision id.
 
932
    """
 
933
    if (unicode_or_utf8_string is None
 
934
        or unicode_or_utf8_string.__class__ == str):
 
935
        return unicode_or_utf8_string
 
936
    if warn:
 
937
        symbol_versioning.warn(_revision_id_warning, DeprecationWarning,
 
938
                               stacklevel=2)
 
939
    return cache_utf8.encode(unicode_or_utf8_string)
 
940
 
 
941
 
 
942
_file_id_warning = ('Unicode file ids were deprecated in bzr 0.15. File id'
 
943
                    ' generators should be creating utf8 file ids.')
 
944
 
 
945
 
 
946
def safe_file_id(unicode_or_utf8_string, warn=True):
 
947
    """File ids should now be utf8, but at one point they were unicode.
 
948
 
 
949
    This is the same as safe_utf8, except it uses the cached encode functions
 
950
    to save a little bit of performance.
 
951
 
 
952
    :param unicode_or_utf8_string: A possibly Unicode file_id. (can also be
 
953
        utf8 or None).
 
954
    :param warn: Functions that are sanitizing user data can set warn=False
 
955
    :return: None or a utf8 file id.
 
956
    """
 
957
    if (unicode_or_utf8_string is None
 
958
        or unicode_or_utf8_string.__class__ == str):
 
959
        return unicode_or_utf8_string
 
960
    if warn:
 
961
        symbol_versioning.warn(_file_id_warning, DeprecationWarning,
 
962
                               stacklevel=2)
 
963
    return cache_utf8.encode(unicode_or_utf8_string)
801
964
 
802
965
 
803
966
_platform_normalizes_filenames = False
847
1010
def terminal_width():
848
1011
    """Return estimated terminal width."""
849
1012
    if sys.platform == 'win32':
850
 
        import bzrlib.win32console
851
 
        return bzrlib.win32console.get_console_size()[0]
 
1013
        return win32utils.get_console_size()[0]
852
1014
    width = 0
853
1015
    try:
854
1016
        import struct, fcntl, termios
872
1034
    return sys.platform != "win32"
873
1035
 
874
1036
 
 
1037
def supports_posix_readonly():
 
1038
    """Return True if 'readonly' has POSIX semantics, False otherwise.
 
1039
 
 
1040
    Notably, a win32 readonly file cannot be deleted, unlike POSIX where the
 
1041
    directory controls creation/deletion, etc.
 
1042
 
 
1043
    And under win32, readonly means that the directory itself cannot be
 
1044
    deleted.  The contents of a readonly directory can be changed, unlike POSIX
 
1045
    where files in readonly directories cannot be added, deleted or renamed.
 
1046
    """
 
1047
    return sys.platform != "win32"
 
1048
 
 
1049
 
875
1050
def set_or_unset_env(env_variable, value):
876
1051
    """Modify the environment, setting or removing the env_variable.
877
1052
 
902
1077
    if sys.platform != "win32":
903
1078
        return
904
1079
    if _validWin32PathRE.match(path) is None:
905
 
        raise IllegalPath(path)
 
1080
        raise errors.IllegalPath(path)
906
1081
 
907
1082
 
908
1083
def walkdirs(top, prefix=""):
914
1089
    
915
1090
    The data yielded is of the form:
916
1091
    ((directory-relpath, directory-path-from-top),
917
 
    [(relpath, basename, kind, lstat), ...]),
 
1092
    [(directory-relpath, basename, kind, lstat, path-from-top), ...]),
918
1093
     - directory-relpath is the relative path of the directory being returned
919
1094
       with respect to top. prefix is prepended to this.
920
1095
     - directory-path-from-root is the path including top for this directory. 
938
1113
    # depending on top and prefix - i.e. ./foo and foo as a pair leads to
939
1114
    # potentially confusing output. We should make this more robust - but
940
1115
    # not at a speed cost. RBC 20060731
941
 
    lstat = os.lstat
942
 
    pending = []
 
1116
    _lstat = os.lstat
943
1117
    _directory = _directory_kind
944
 
    _listdir = listdir
945
 
    pending = [(prefix, "", _directory, None, top)]
 
1118
    _listdir = os.listdir
 
1119
    _kind_from_mode = _formats.get
 
1120
    pending = [(safe_unicode(prefix), "", _directory, None, safe_unicode(top))]
946
1121
    while pending:
947
 
        dirblock = []
948
 
        currentdir = pending.pop()
949
1122
        # 0 - relpath, 1- basename, 2- kind, 3- stat, 4-toppath
950
 
        top = currentdir[4]
951
 
        if currentdir[0]:
952
 
            relroot = currentdir[0] + '/'
953
 
        else:
954
 
            relroot = ""
955
 
        for name in sorted(_listdir(top)):
956
 
            abspath = top + '/' + name
957
 
            statvalue = lstat(abspath)
958
 
            dirblock.append((relroot + name, name,
959
 
                file_kind_from_stat_mode(statvalue.st_mode),
960
 
                statvalue, abspath))
961
 
        yield (currentdir[0], top), dirblock
962
 
        # push the user specified dirs from dirblock
963
 
        for dir in reversed(dirblock):
964
 
            if dir[2] == _directory:
965
 
                pending.append(dir)
 
1123
        relroot, _, _, _, top = pending.pop()
 
1124
        if relroot:
 
1125
            relprefix = relroot + u'/'
 
1126
        else:
 
1127
            relprefix = ''
 
1128
        top_slash = top + u'/'
 
1129
 
 
1130
        dirblock = []
 
1131
        append = dirblock.append
 
1132
        for name in sorted(_listdir(top)):
 
1133
            abspath = top_slash + name
 
1134
            statvalue = _lstat(abspath)
 
1135
            kind = _kind_from_mode(statvalue.st_mode & 0170000, 'unknown')
 
1136
            append((relprefix + name, name, kind, statvalue, abspath))
 
1137
        yield (relroot, top), dirblock
 
1138
 
 
1139
        # push the user specified dirs from dirblock
 
1140
        pending.extend(d for d in reversed(dirblock) if d[2] == _directory)
 
1141
 
 
1142
 
 
1143
def _walkdirs_utf8(top, prefix=""):
 
1144
    """Yield data about all the directories in a tree.
 
1145
 
 
1146
    This yields the same information as walkdirs() only each entry is yielded
 
1147
    in utf-8. On platforms which have a filesystem encoding of utf8 the paths
 
1148
    are returned as exact byte-strings.
 
1149
 
 
1150
    :return: yields a tuple of (dir_info, [file_info])
 
1151
        dir_info is (utf8_relpath, path-from-top)
 
1152
        file_info is (utf8_relpath, utf8_name, kind, lstat, path-from-top)
 
1153
        if top is an absolute path, path-from-top is also an absolute path.
 
1154
        path-from-top might be unicode or utf8, but it is the correct path to
 
1155
        pass to os functions to affect the file in question. (such as os.lstat)
 
1156
    """
 
1157
    fs_encoding = _fs_enc.upper()
 
1158
    if (sys.platform == 'win32' or
 
1159
        fs_encoding not in ('UTF-8', 'US-ASCII', 'ANSI_X3.4-1968')): # ascii
 
1160
        return _walkdirs_unicode_to_utf8(top, prefix=prefix)
 
1161
    else:
 
1162
        return _walkdirs_fs_utf8(top, prefix=prefix)
 
1163
 
 
1164
 
 
1165
def _walkdirs_fs_utf8(top, prefix=""):
 
1166
    """See _walkdirs_utf8.
 
1167
 
 
1168
    This sub-function is called when we know the filesystem is already in utf8
 
1169
    encoding. So we don't need to transcode filenames.
 
1170
    """
 
1171
    _lstat = os.lstat
 
1172
    _directory = _directory_kind
 
1173
    _listdir = os.listdir
 
1174
    _kind_from_mode = _formats.get
 
1175
 
 
1176
    # 0 - relpath, 1- basename, 2- kind, 3- stat, 4-toppath
 
1177
    # But we don't actually uses 1-3 in pending, so set them to None
 
1178
    pending = [(safe_utf8(prefix), None, None, None, safe_utf8(top))]
 
1179
    while pending:
 
1180
        relroot, _, _, _, top = pending.pop()
 
1181
        if relroot:
 
1182
            relprefix = relroot + '/'
 
1183
        else:
 
1184
            relprefix = ''
 
1185
        top_slash = top + '/'
 
1186
 
 
1187
        dirblock = []
 
1188
        append = dirblock.append
 
1189
        for name in sorted(_listdir(top)):
 
1190
            abspath = top_slash + name
 
1191
            statvalue = _lstat(abspath)
 
1192
            kind = _kind_from_mode(statvalue.st_mode & 0170000, 'unknown')
 
1193
            append((relprefix + name, name, kind, statvalue, abspath))
 
1194
        yield (relroot, top), dirblock
 
1195
 
 
1196
        # push the user specified dirs from dirblock
 
1197
        pending.extend(d for d in reversed(dirblock) if d[2] == _directory)
 
1198
 
 
1199
 
 
1200
def _walkdirs_unicode_to_utf8(top, prefix=""):
 
1201
    """See _walkdirs_utf8
 
1202
 
 
1203
    Because Win32 has a Unicode api, all of the 'path-from-top' entries will be
 
1204
    Unicode paths.
 
1205
    This is currently the fallback code path when the filesystem encoding is
 
1206
    not UTF-8. It may be better to implement an alternative so that we can
 
1207
    safely handle paths that are not properly decodable in the current
 
1208
    encoding.
 
1209
    """
 
1210
    _utf8_encode = codecs.getencoder('utf8')
 
1211
    _lstat = os.lstat
 
1212
    _directory = _directory_kind
 
1213
    _listdir = os.listdir
 
1214
    _kind_from_mode = _formats.get
 
1215
 
 
1216
    pending = [(safe_utf8(prefix), None, None, None, safe_unicode(top))]
 
1217
    while pending:
 
1218
        relroot, _, _, _, top = pending.pop()
 
1219
        if relroot:
 
1220
            relprefix = relroot + '/'
 
1221
        else:
 
1222
            relprefix = ''
 
1223
        top_slash = top + u'/'
 
1224
 
 
1225
        dirblock = []
 
1226
        append = dirblock.append
 
1227
        for name in sorted(_listdir(top)):
 
1228
            name_utf8 = _utf8_encode(name)[0]
 
1229
            abspath = top_slash + name
 
1230
            statvalue = _lstat(abspath)
 
1231
            kind = _kind_from_mode(statvalue.st_mode & 0170000, 'unknown')
 
1232
            append((relprefix + name_utf8, name_utf8, kind, statvalue, abspath))
 
1233
        yield (relroot, top), dirblock
 
1234
 
 
1235
        # push the user specified dirs from dirblock
 
1236
        pending.extend(d for d in reversed(dirblock) if d[2] == _directory)
966
1237
 
967
1238
 
968
1239
def copy_tree(from_path, to_path, handlers={}):
1025
1296
_cached_user_encoding = None
1026
1297
 
1027
1298
 
1028
 
def get_user_encoding():
 
1299
def get_user_encoding(use_cache=True):
1029
1300
    """Find out what the preferred user encoding is.
1030
1301
 
1031
1302
    This is generally the encoding that is used for command line parameters
1032
1303
    and file contents. This may be different from the terminal encoding
1033
1304
    or the filesystem encoding.
1034
1305
 
 
1306
    :param  use_cache:  Enable cache for detected encoding.
 
1307
                        (This parameter is turned on by default,
 
1308
                        and required only for selftesting)
 
1309
 
1035
1310
    :return: A string defining the preferred user encoding
1036
1311
    """
1037
1312
    global _cached_user_encoding
1038
 
    if _cached_user_encoding is not None:
 
1313
    if _cached_user_encoding is not None and use_cache:
1039
1314
        return _cached_user_encoding
1040
1315
 
1041
1316
    if sys.platform == 'darwin':
1049
1324
        import locale
1050
1325
 
1051
1326
    try:
1052
 
        _cached_user_encoding = locale.getpreferredencoding()
 
1327
        user_encoding = locale.getpreferredencoding()
1053
1328
    except locale.Error, e:
1054
1329
        sys.stderr.write('bzr: warning: %s\n'
1055
 
                         '  Could not what text encoding to use.\n'
 
1330
                         '  Could not determine what text encoding to use.\n'
1056
1331
                         '  This error usually means your Python interpreter\n'
1057
1332
                         '  doesn\'t support the locale set by $LANG (%s)\n'
1058
1333
                         "  Continuing with ascii encoding.\n"
1059
1334
                         % (e, os.environ.get('LANG')))
1060
 
 
1061
 
    if _cached_user_encoding is None:
1062
 
        _cached_user_encoding = 'ascii'
1063
 
    return _cached_user_encoding
 
1335
        user_encoding = 'ascii'
 
1336
 
 
1337
    # Windows returns 'cp0' to indicate there is no code page. So we'll just
 
1338
    # treat that as ASCII, and not support printing unicode characters to the
 
1339
    # console.
 
1340
    if user_encoding in (None, 'cp0'):
 
1341
        user_encoding = 'ascii'
 
1342
    else:
 
1343
        # check encoding
 
1344
        try:
 
1345
            codecs.lookup(user_encoding)
 
1346
        except LookupError:
 
1347
            sys.stderr.write('bzr: warning:'
 
1348
                             ' unknown encoding %s.'
 
1349
                             ' Continuing with ascii encoding.\n'
 
1350
                             % user_encoding
 
1351
                            )
 
1352
            user_encoding = 'ascii'
 
1353
 
 
1354
    if use_cache:
 
1355
        _cached_user_encoding = user_encoding
 
1356
 
 
1357
    return user_encoding
 
1358
 
 
1359
 
 
1360
def recv_all(socket, bytes):
 
1361
    """Receive an exact number of bytes.
 
1362
 
 
1363
    Regular Socket.recv() may return less than the requested number of bytes,
 
1364
    dependning on what's in the OS buffer.  MSG_WAITALL is not available
 
1365
    on all platforms, but this should work everywhere.  This will return
 
1366
    less than the requested amount if the remote end closes.
 
1367
 
 
1368
    This isn't optimized and is intended mostly for use in testing.
 
1369
    """
 
1370
    b = ''
 
1371
    while len(b) < bytes:
 
1372
        new = socket.recv(bytes - len(b))
 
1373
        if new == '':
 
1374
            break # eof
 
1375
        b += new
 
1376
    return b
 
1377
 
 
1378
def dereference_path(path):
 
1379
    """Determine the real path to a file.
 
1380
 
 
1381
    All parent elements are dereferenced.  But the file itself is not
 
1382
    dereferenced.
 
1383
    :param path: The original path.  May be absolute or relative.
 
1384
    :return: the real path *to* the file
 
1385
    """
 
1386
    parent, base = os.path.split(path)
 
1387
    # The pathjoin for '.' is a workaround for Python bug #1213894.
 
1388
    # (initial path components aren't dereferenced)
 
1389
    return pathjoin(realpath(pathjoin('.', parent)), base)