~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/osutils.py

  • Committer: Matthieu Moy
  • Date: 2006-07-08 19:32:30 UTC
  • mfrom: (1845 +trunk)
  • mto: This revision was merged to the branch mainline in revision 1857.
  • Revision ID: Matthieu.Moy@imag.fr-20060708193230-3eb72d871471bd5b
merge

Show diffs side-by-side

added added

removed removed

Lines of Context:
16
16
# along with this program; if not, write to the Free Software
17
17
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18
18
 
19
 
from shutil import copyfile
20
 
from stat import (S_ISREG, S_ISDIR, S_ISLNK, ST_MODE, ST_SIZE,
21
 
                  S_ISCHR, S_ISBLK, S_ISFIFO, S_ISSOCK)
22
19
from cStringIO import StringIO
23
20
import errno
 
21
from ntpath import (abspath as _nt_abspath,
 
22
                    join as _nt_join,
 
23
                    normpath as _nt_normpath,
 
24
                    realpath as _nt_realpath,
 
25
                    splitdrive as _nt_splitdrive,
 
26
                    )
24
27
import os
 
28
from os import listdir
 
29
import posixpath
25
30
import re
26
31
import sha
27
32
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)
28
37
import string
29
38
import sys
30
39
import time
31
40
import types
32
41
import tempfile
 
42
import unicodedata
33
43
 
34
44
import bzrlib
35
45
from bzrlib.errors import (BzrError,
38
48
                           PathNotChild,
39
49
                           IllegalPath,
40
50
                           )
 
51
from bzrlib.symbol_versioning import (deprecated_function, 
 
52
        zero_nine)
41
53
from bzrlib.trace import mutter
42
 
import bzrlib.win32console
43
54
 
44
55
 
45
56
def make_readonly(filename):
74
85
        return f
75
86
 
76
87
 
77
 
def file_kind(f):
78
 
    mode = os.lstat(f)[ST_MODE]
79
 
    if S_ISREG(mode):
80
 
        return 'file'
81
 
    elif S_ISDIR(mode):
82
 
        return 'directory'
83
 
    elif S_ISLNK(mode):
84
 
        return 'symlink'
85
 
    elif S_ISCHR(mode):
86
 
        return 'chardev'
87
 
    elif S_ISBLK(mode):
88
 
        return 'block'
89
 
    elif S_ISFIFO(mode):
90
 
        return 'fifo'
91
 
    elif S_ISSOCK(mode):
92
 
        return 'socket'
93
 
    else:
94
 
        return 'unknown'
 
88
_directory_kind = 'directory'
 
89
 
 
90
_formats = {
 
91
    stat.S_IFDIR:_directory_kind,
 
92
    stat.S_IFCHR:'chardev',
 
93
    stat.S_IFBLK:'block',
 
94
    stat.S_IFREG:'file',
 
95
    stat.S_IFIFO:'fifo',
 
96
    stat.S_IFLNK:'symlink',
 
97
    stat.S_IFSOCK:'socket',
 
98
}
 
99
 
 
100
 
 
101
def file_kind_from_stat_mode(stat_mode, _formats=_formats, _unknown='unknown'):
 
102
    """Generate a file kind from a stat mode. This is used in walkdirs.
 
103
 
 
104
    Its performance is critical: Do not mutate without careful benchmarking.
 
105
    """
 
106
    try:
 
107
        return _formats[stat_mode & 0170000]
 
108
    except KeyError:
 
109
        return _unknown
 
110
 
 
111
 
 
112
def file_kind(f, _lstat=os.lstat, _mapper=file_kind_from_stat_mode):
 
113
    try:
 
114
        return _mapper(_lstat(f).st_mode)
 
115
    except OSError, e:
 
116
        if getattr(e, 'errno', None) == errno.ENOENT:
 
117
            raise bzrlib.errors.NoSuchFile(f)
 
118
        raise
95
119
 
96
120
 
97
121
def kind_marker(kind):
98
122
    if kind == 'file':
99
123
        return ''
100
 
    elif kind == 'directory':
 
124
    elif kind == _directory_kind:
101
125
        return '/'
102
126
    elif kind == 'symlink':
103
127
        return '@'
104
128
    else:
105
129
        raise BzrError('invalid file kind %r' % kind)
106
130
 
107
 
def lexists(f):
108
 
    if hasattr(os.path, 'lexists'):
109
 
        return os.path.lexists(f)
110
 
    try:
111
 
        if hasattr(os, 'lstat'):
112
 
            os.lstat(f)
113
 
        else:
114
 
            os.stat(f)
115
 
        return True
116
 
    except OSError,e:
117
 
        if e.errno == errno.ENOENT:
118
 
            return False;
119
 
        else:
120
 
            raise BzrError("lstat/stat of (%r): %r" % (f, e))
 
131
lexists = getattr(os.path, 'lexists', None)
 
132
if lexists is None:
 
133
    def lexists(f):
 
134
        try:
 
135
            if hasattr(os, 'lstat'):
 
136
                os.lstat(f)
 
137
            else:
 
138
                os.stat(f)
 
139
            return True
 
140
        except OSError,e:
 
141
            if e.errno == errno.ENOENT:
 
142
                return False;
 
143
            else:
 
144
                raise BzrError("lstat/stat of (%r): %r" % (f, e))
 
145
 
121
146
 
122
147
def fancy_rename(old, new, rename_func, unlink_func):
123
148
    """A fancy rename, when you don't have atomic rename.
173
198
            else:
174
199
                rename_func(tmp_name, new)
175
200
 
 
201
 
 
202
# In Python 2.4.2 and older, os.path.abspath and os.path.realpath
 
203
# choke on a Unicode string containing a relative path if
 
204
# os.getcwd() returns a non-sys.getdefaultencoding()-encoded
 
205
# string.
 
206
_fs_enc = sys.getfilesystemencoding()
 
207
def _posix_abspath(path):
 
208
    # jam 20060426 rather than encoding to fsencoding
 
209
    # copy posixpath.abspath, but use os.getcwdu instead
 
210
    if not posixpath.isabs(path):
 
211
        path = posixpath.join(getcwd(), path)
 
212
    return posixpath.normpath(path)
 
213
 
 
214
 
 
215
def _posix_realpath(path):
 
216
    return posixpath.realpath(path.encode(_fs_enc)).decode(_fs_enc)
 
217
 
 
218
 
 
219
def _win32_fixdrive(path):
 
220
    """Force drive letters to be consistent.
 
221
 
 
222
    win32 is inconsistent whether it returns lower or upper case
 
223
    and even if it was consistent the user might type the other
 
224
    so we force it to uppercase
 
225
    running python.exe under cmd.exe return capital C:\\
 
226
    running win32 python inside a cygwin shell returns lowercase c:\\
 
227
    """
 
228
    drive, path = _nt_splitdrive(path)
 
229
    return drive.upper() + path
 
230
 
 
231
 
 
232
def _win32_abspath(path):
 
233
    # Real _nt_abspath doesn't have a problem with a unicode cwd
 
234
    return _win32_fixdrive(_nt_abspath(unicode(path)).replace('\\', '/'))
 
235
 
 
236
 
 
237
def _win32_realpath(path):
 
238
    # Real _nt_realpath doesn't have a problem with a unicode cwd
 
239
    return _win32_fixdrive(_nt_realpath(unicode(path)).replace('\\', '/'))
 
240
 
 
241
 
 
242
def _win32_pathjoin(*args):
 
243
    return _nt_join(*args).replace('\\', '/')
 
244
 
 
245
 
 
246
def _win32_normpath(path):
 
247
    return _win32_fixdrive(_nt_normpath(unicode(path)).replace('\\', '/'))
 
248
 
 
249
 
 
250
def _win32_getcwd():
 
251
    return _win32_fixdrive(os.getcwdu().replace('\\', '/'))
 
252
 
 
253
 
 
254
def _win32_mkdtemp(*args, **kwargs):
 
255
    return _win32_fixdrive(tempfile.mkdtemp(*args, **kwargs).replace('\\', '/'))
 
256
 
 
257
 
 
258
def _win32_rename(old, new):
 
259
    """We expect to be able to atomically replace 'new' with old.
 
260
 
 
261
    On win32, if new exists, it must be moved out of the way first,
 
262
    and then deleted. 
 
263
    """
 
264
    try:
 
265
        fancy_rename(old, new, rename_func=os.rename, unlink_func=os.unlink)
 
266
    except OSError, e:
 
267
        if e.errno in (errno.EPERM, errno.EACCES, errno.EBUSY):
 
268
            # If we try to rename a non-existant file onto cwd, we get EPERM
 
269
            # instead of ENOENT, this will raise ENOENT if the old path
 
270
            # doesn't exist
 
271
            os.lstat(old)
 
272
        raise
 
273
 
 
274
 
176
275
# Default is to just use the python builtins, but these can be rebound on
177
276
# particular platforms.
178
 
abspath = os.path.abspath
179
 
realpath = os.path.realpath
 
277
abspath = _posix_abspath
 
278
realpath = _posix_realpath
180
279
pathjoin = os.path.join
181
280
normpath = os.path.normpath
182
281
getcwd = os.getcwdu
188
287
 
189
288
MIN_ABS_PATHLENGTH = 1
190
289
 
191
 
if os.name == "posix":
192
 
    # In Python 2.4.2 and older, os.path.abspath and os.path.realpath
193
 
    # choke on a Unicode string containing a relative path if
194
 
    # os.getcwd() returns a non-sys.getdefaultencoding()-encoded
195
 
    # string.
196
 
    _fs_enc = sys.getfilesystemencoding()
197
 
    def abspath(path):
198
 
        return os.path.abspath(path.encode(_fs_enc)).decode(_fs_enc)
199
 
 
200
 
    def realpath(path):
201
 
        return os.path.realpath(path.encode(_fs_enc)).decode(_fs_enc)
202
290
 
203
291
if sys.platform == 'win32':
204
 
    # We need to use the Unicode-aware os.path.abspath and
205
 
    # os.path.realpath on Windows systems.
206
 
    def abspath(path):
207
 
        return os.path.abspath(path).replace('\\', '/')
208
 
 
209
 
    def realpath(path):
210
 
        return os.path.realpath(path).replace('\\', '/')
211
 
 
212
 
    def pathjoin(*args):
213
 
        return os.path.join(*args).replace('\\', '/')
214
 
 
215
 
    def normpath(path):
216
 
        return os.path.normpath(path).replace('\\', '/')
217
 
 
218
 
    def getcwd():
219
 
        return os.getcwdu().replace('\\', '/')
220
 
 
221
 
    def mkdtemp(*args, **kwargs):
222
 
        return tempfile.mkdtemp(*args, **kwargs).replace('\\', '/')
223
 
 
224
 
    def rename(old, new):
225
 
        fancy_rename(old, new, rename_func=os.rename, unlink_func=os.unlink)
 
292
    abspath = _win32_abspath
 
293
    realpath = _win32_realpath
 
294
    pathjoin = _win32_pathjoin
 
295
    normpath = _win32_normpath
 
296
    getcwd = _win32_getcwd
 
297
    mkdtemp = _win32_mkdtemp
 
298
    rename = _win32_rename
226
299
 
227
300
    MIN_ABS_PATHLENGTH = 3
228
301
 
244
317
        return shutil.rmtree(path, ignore_errors, onerror)
245
318
 
246
319
 
 
320
def get_terminal_encoding():
 
321
    """Find the best encoding for printing to the screen.
 
322
 
 
323
    This attempts to check both sys.stdout and sys.stdin to see
 
324
    what encoding they are in, and if that fails it falls back to
 
325
    bzrlib.user_encoding.
 
326
    The problem is that on Windows, locale.getpreferredencoding()
 
327
    is not the same encoding as that used by the console:
 
328
    http://mail.python.org/pipermail/python-list/2003-May/162357.html
 
329
 
 
330
    On my standard US Windows XP, the preferred encoding is
 
331
    cp1252, but the console is cp437
 
332
    """
 
333
    output_encoding = getattr(sys.stdout, 'encoding', None)
 
334
    if not output_encoding:
 
335
        input_encoding = getattr(sys.stdin, 'encoding', None)
 
336
        if not input_encoding:
 
337
            output_encoding = bzrlib.user_encoding
 
338
            mutter('encoding stdout as bzrlib.user_encoding %r', output_encoding)
 
339
        else:
 
340
            output_encoding = input_encoding
 
341
            mutter('encoding stdout as sys.stdin encoding %r', output_encoding)
 
342
    else:
 
343
        mutter('encoding stdout as sys.stdout encoding %r', output_encoding)
 
344
    return output_encoding
 
345
 
 
346
 
247
347
def normalizepath(f):
248
348
    if hasattr(os.path, 'realpath'):
249
349
        F = realpath
352
452
        return False
353
453
 
354
454
 
 
455
def is_inside_or_parent_of_any(dir_list, fname):
 
456
    """True if fname is a child or a parent of any of the given files."""
 
457
    for dirname in dir_list:
 
458
        if is_inside(dirname, fname) or is_inside(fname, dirname):
 
459
            return True
 
460
    else:
 
461
        return False
 
462
 
 
463
 
355
464
def pumpfile(fromfile, tofile):
356
465
    """Copy contents of one file to another."""
357
466
    BUFSIZE = 32768
547
656
    return pathjoin(*p)
548
657
 
549
658
 
 
659
@deprecated_function(zero_nine)
550
660
def appendpath(p1, p2):
551
661
    if p1 == '':
552
662
        return p2
629
739
    assert len(base) >= MIN_ABS_PATHLENGTH, ('Length of base must be equal or'
630
740
        ' exceed the platform minimum length (which is %d)' % 
631
741
        MIN_ABS_PATHLENGTH)
 
742
 
632
743
    rp = abspath(path)
633
744
 
634
745
    s = []
640
751
        if tail:
641
752
            s.insert(0, tail)
642
753
    else:
643
 
        # XXX This should raise a NotChildPath exception, as its not tied
644
 
        # to branch anymore.
645
754
        raise PathNotChild(rp, base)
646
755
 
647
756
    if s:
666
775
        raise BzrBadParameterNotUnicode(unicode_or_utf8_string)
667
776
 
668
777
 
 
778
_platform_normalizes_filenames = False
 
779
if sys.platform == 'darwin':
 
780
    _platform_normalizes_filenames = True
 
781
 
 
782
 
 
783
def normalizes_filenames():
 
784
    """Return True if this platform normalizes unicode filenames.
 
785
 
 
786
    Mac OSX does, Windows/Linux do not.
 
787
    """
 
788
    return _platform_normalizes_filenames
 
789
 
 
790
 
 
791
if _platform_normalizes_filenames:
 
792
    def unicode_filename(path):
 
793
        """Make sure 'path' is a properly normalized filename.
 
794
 
 
795
        On platforms where the system normalizes filenames (Mac OSX),
 
796
        you can access a file by any path which will normalize
 
797
        correctly.
 
798
        Internally, bzr only supports NFC/NFKC normalization, since
 
799
        that is the standard for XML documents.
 
800
        So we return an normalized path, and indicate this has been
 
801
        properly normalized.
 
802
 
 
803
        :return: (path, is_normalized) Return a path which can
 
804
                access the file, and whether or not this path is
 
805
                normalized.
 
806
        """
 
807
        return unicodedata.normalize('NFKC', path), True
 
808
else:
 
809
    def unicode_filename(path):
 
810
        """Make sure 'path' is a properly normalized filename.
 
811
 
 
812
        On platforms where the system does not normalize filenames 
 
813
        (Windows, Linux), you have to access a file by its exact path.
 
814
        Internally, bzr only supports NFC/NFKC normalization, since
 
815
        that is the standard for XML documents.
 
816
        So we return the original path, and indicate if this is
 
817
        properly normalized.
 
818
 
 
819
        :return: (path, is_normalized) Return a path which can
 
820
                access the file, and whether or not this path is
 
821
                normalized.
 
822
        """
 
823
        return path, unicodedata.normalize('NFKC', path) == path
 
824
 
 
825
 
669
826
def terminal_width():
670
827
    """Return estimated terminal width."""
671
828
    if sys.platform == 'win32':
693
850
    return sys.platform != "win32"
694
851
 
695
852
 
696
 
def strip_trailing_slash(path):
697
 
    """Strip trailing slash, except for root paths.
698
 
    The definition of 'root path' is platform-dependent.
699
 
    """
700
 
    if len(path) != MIN_ABS_PATHLENGTH and path[-1] == '/':
701
 
        return path[:-1]
702
 
    else:
703
 
        return path
704
 
 
705
 
 
706
853
_validWin32PathRE = re.compile(r'^([A-Za-z]:[/\\])?[^:<>*"?\|]*$')
707
854
 
708
855
 
715
862
        return
716
863
    if _validWin32PathRE.match(path) is None:
717
864
        raise IllegalPath(path)
 
865
 
 
866
 
 
867
def walkdirs(top, prefix=""):
 
868
    """Yield data about all the directories in a tree.
 
869
    
 
870
    This yields all the data about the contents of a directory at a time.
 
871
    After each directory has been yielded, if the caller has mutated the list
 
872
    to exclude some directories, they are then not descended into.
 
873
    
 
874
    The data yielded is of the form:
 
875
    [(relpath, basename, kind, lstat, path_from_top), ...]
 
876
 
 
877
    :param prefix: Prefix the relpaths that are yielded with 'prefix'. This 
 
878
        allows one to walk a subtree but get paths that are relative to a tree
 
879
        rooted higher up.
 
880
    :return: an iterator over the dirs.
 
881
    """
 
882
    lstat = os.lstat
 
883
    pending = []
 
884
    _directory = _directory_kind
 
885
    _listdir = listdir
 
886
    pending = [(prefix, "", _directory, None, top)]
 
887
    while pending:
 
888
        dirblock = []
 
889
        currentdir = pending.pop()
 
890
        # 0 - relpath, 1- basename, 2- kind, 3- stat, 4-toppath
 
891
        top = currentdir[4]
 
892
        if currentdir[0]:
 
893
            relroot = currentdir[0] + '/'
 
894
        else:
 
895
            relroot = ""
 
896
        for name in sorted(_listdir(top)):
 
897
            abspath = top + '/' + name
 
898
            statvalue = lstat(abspath)
 
899
            dirblock.append ((relroot + name, name, file_kind_from_stat_mode(statvalue.st_mode), statvalue, abspath))
 
900
        yield dirblock
 
901
        # push the user specified dirs from dirblock
 
902
        for dir in reversed(dirblock):
 
903
            if dir[2] == _directory:
 
904
                pending.append(dir)
 
905
 
 
906
 
 
907
def path_prefix_key(path):
 
908
    """Generate a prefix-order path key for path.
 
909
 
 
910
    This can be used to sort paths in the same way that walkdirs does.
 
911
    """
 
912
    return (dirname(path) , path)
 
913
 
 
914
 
 
915
def compare_paths_prefix_order(path_a, path_b):
 
916
    """Compare path_a and path_b to generate the same order walkdirs uses."""
 
917
    key_a = path_prefix_key(path_a)
 
918
    key_b = path_prefix_key(path_b)
 
919
    return cmp(key_a, key_b)