~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/osutils.py

  • Committer: Martin Pool
  • Date: 2006-06-20 07:55:43 UTC
  • mfrom: (1798 +trunk)
  • mto: This revision was merged to the branch mainline in revision 1799.
  • Revision ID: mbp@sourcefrog.net-20060620075543-b10f6575d4a4fa32
[merge] bzr.dev

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
                    )
24
26
import os
 
27
from os import listdir
 
28
import posixpath
25
29
import re
26
30
import sha
27
31
import shutil
 
32
from shutil import copyfile
28
33
import stat
 
34
from stat import (S_ISREG, S_ISDIR, S_ISLNK, ST_MODE, ST_SIZE,
 
35
                  S_ISCHR, S_ISBLK, S_ISFIFO, S_ISSOCK)
29
36
import string
30
37
import sys
31
38
import time
32
39
import types
33
40
import tempfile
 
41
import unicodedata
34
42
 
35
43
import bzrlib
36
44
from bzrlib.errors import (BzrError,
39
47
                           PathNotChild,
40
48
                           IllegalPath,
41
49
                           )
42
 
from bzrlib.symbol_versioning import *
 
50
from bzrlib.symbol_versioning import (deprecated_function, 
 
51
        zero_nine)
43
52
from bzrlib.trace import mutter
44
 
import bzrlib.win32console
45
53
 
46
54
 
47
55
def make_readonly(filename):
76
84
        return f
77
85
 
78
86
 
 
87
_directory_kind = 'directory'
 
88
 
79
89
_formats = {
80
 
    stat.S_IFDIR:'directory',
 
90
    stat.S_IFDIR:_directory_kind,
81
91
    stat.S_IFCHR:'chardev',
82
92
    stat.S_IFBLK:'block',
83
93
    stat.S_IFREG:'file',
85
95
    stat.S_IFLNK:'symlink',
86
96
    stat.S_IFSOCK:'socket',
87
97
}
88
 
def file_kind(f, _formats=_formats, _unknown='unknown', _lstat=os.lstat):
 
98
 
 
99
 
 
100
def file_kind_from_stat_mode(stat_mode, _formats=_formats, _unknown='unknown'):
 
101
    """Generate a file kind from a stat mode. This is used in walkdirs.
 
102
 
 
103
    Its performance is critical: Do not mutate without careful benchmarking.
 
104
    """
89
105
    try:
90
 
        return _formats[_lstat(f).st_mode & 0170000]
 
106
        return _formats[stat_mode & 0170000]
91
107
    except KeyError:
92
108
        return _unknown
93
109
 
94
110
 
 
111
def file_kind(f, _lstat=os.lstat, _mapper=file_kind_from_stat_mode):
 
112
    try:
 
113
        return _mapper(_lstat(f).st_mode)
 
114
    except OSError, e:
 
115
        if getattr(e, 'errno', None) == errno.ENOENT:
 
116
            raise bzrlib.errors.NoSuchFile(f)
 
117
        raise
 
118
 
 
119
 
95
120
def kind_marker(kind):
96
121
    if kind == 'file':
97
122
        return ''
98
 
    elif kind == 'directory':
 
123
    elif kind == _directory_kind:
99
124
        return '/'
100
125
    elif kind == 'symlink':
101
126
        return '@'
172
197
            else:
173
198
                rename_func(tmp_name, new)
174
199
 
 
200
 
 
201
# In Python 2.4.2 and older, os.path.abspath and os.path.realpath
 
202
# choke on a Unicode string containing a relative path if
 
203
# os.getcwd() returns a non-sys.getdefaultencoding()-encoded
 
204
# string.
 
205
_fs_enc = sys.getfilesystemencoding()
 
206
def _posix_abspath(path):
 
207
    # jam 20060426 rather than encoding to fsencoding
 
208
    # copy posixpath.abspath, but use os.getcwdu instead
 
209
    if not posixpath.isabs(path):
 
210
        path = posixpath.join(getcwd(), path)
 
211
    return posixpath.normpath(path)
 
212
 
 
213
 
 
214
def _posix_realpath(path):
 
215
    return posixpath.realpath(path.encode(_fs_enc)).decode(_fs_enc)
 
216
 
 
217
 
 
218
def _win32_abspath(path):
 
219
    # Real _nt_abspath doesn't have a problem with a unicode cwd
 
220
    return _nt_abspath(unicode(path)).replace('\\', '/')
 
221
 
 
222
 
 
223
def _win32_realpath(path):
 
224
    # Real _nt_realpath doesn't have a problem with a unicode cwd
 
225
    return _nt_realpath(unicode(path)).replace('\\', '/')
 
226
 
 
227
 
 
228
def _win32_pathjoin(*args):
 
229
    return _nt_join(*args).replace('\\', '/')
 
230
 
 
231
 
 
232
def _win32_normpath(path):
 
233
    return _nt_normpath(unicode(path)).replace('\\', '/')
 
234
 
 
235
 
 
236
def _win32_getcwd():
 
237
    return os.getcwdu().replace('\\', '/')
 
238
 
 
239
 
 
240
def _win32_mkdtemp(*args, **kwargs):
 
241
    return tempfile.mkdtemp(*args, **kwargs).replace('\\', '/')
 
242
 
 
243
 
 
244
def _win32_rename(old, new):
 
245
    fancy_rename(old, new, rename_func=os.rename, unlink_func=os.unlink)
 
246
 
 
247
 
175
248
# Default is to just use the python builtins, but these can be rebound on
176
249
# particular platforms.
177
 
abspath = os.path.abspath
178
 
realpath = os.path.realpath
 
250
abspath = _posix_abspath
 
251
realpath = _posix_realpath
179
252
pathjoin = os.path.join
180
253
normpath = os.path.normpath
181
254
getcwd = os.getcwdu
187
260
 
188
261
MIN_ABS_PATHLENGTH = 1
189
262
 
190
 
if os.name == "posix":
191
 
    # In Python 2.4.2 and older, os.path.abspath and os.path.realpath
192
 
    # choke on a Unicode string containing a relative path if
193
 
    # os.getcwd() returns a non-sys.getdefaultencoding()-encoded
194
 
    # string.
195
 
    _fs_enc = sys.getfilesystemencoding()
196
 
    def abspath(path):
197
 
        return os.path.abspath(path.encode(_fs_enc)).decode(_fs_enc)
198
 
 
199
 
    def realpath(path):
200
 
        return os.path.realpath(path.encode(_fs_enc)).decode(_fs_enc)
201
263
 
202
264
if sys.platform == 'win32':
203
 
    # We need to use the Unicode-aware os.path.abspath and
204
 
    # os.path.realpath on Windows systems.
205
 
    def abspath(path):
206
 
        return os.path.abspath(path).replace('\\', '/')
207
 
 
208
 
    def realpath(path):
209
 
        return os.path.realpath(path).replace('\\', '/')
210
 
 
211
 
    def pathjoin(*args):
212
 
        return os.path.join(*args).replace('\\', '/')
213
 
 
214
 
    def normpath(path):
215
 
        return os.path.normpath(path).replace('\\', '/')
216
 
 
217
 
    def getcwd():
218
 
        return os.getcwdu().replace('\\', '/')
219
 
 
220
 
    def mkdtemp(*args, **kwargs):
221
 
        return tempfile.mkdtemp(*args, **kwargs).replace('\\', '/')
222
 
 
223
 
    def rename(old, new):
224
 
        fancy_rename(old, new, rename_func=os.rename, unlink_func=os.unlink)
 
265
    abspath = _win32_abspath
 
266
    realpath = _win32_realpath
 
267
    pathjoin = _win32_pathjoin
 
268
    normpath = _win32_normpath
 
269
    getcwd = _win32_getcwd
 
270
    mkdtemp = _win32_mkdtemp
 
271
    rename = _win32_rename
225
272
 
226
273
    MIN_ABS_PATHLENGTH = 3
227
274
 
243
290
        return shutil.rmtree(path, ignore_errors, onerror)
244
291
 
245
292
 
 
293
def get_terminal_encoding():
 
294
    """Find the best encoding for printing to the screen.
 
295
 
 
296
    This attempts to check both sys.stdout and sys.stdin to see
 
297
    what encoding they are in, and if that fails it falls back to
 
298
    bzrlib.user_encoding.
 
299
    The problem is that on Windows, locale.getpreferredencoding()
 
300
    is not the same encoding as that used by the console:
 
301
    http://mail.python.org/pipermail/python-list/2003-May/162357.html
 
302
 
 
303
    On my standard US Windows XP, the preferred encoding is
 
304
    cp1252, but the console is cp437
 
305
    """
 
306
    output_encoding = getattr(sys.stdout, 'encoding', None)
 
307
    if not output_encoding:
 
308
        input_encoding = getattr(sys.stdin, 'encoding', None)
 
309
        if not input_encoding:
 
310
            output_encoding = bzrlib.user_encoding
 
311
            mutter('encoding stdout as bzrlib.user_encoding %r', output_encoding)
 
312
        else:
 
313
            output_encoding = input_encoding
 
314
            mutter('encoding stdout as sys.stdin encoding %r', output_encoding)
 
315
    else:
 
316
        mutter('encoding stdout as sys.stdout encoding %r', output_encoding)
 
317
    return output_encoding
 
318
 
 
319
 
246
320
def normalizepath(f):
247
321
    if hasattr(os.path, 'realpath'):
248
322
        F = realpath
351
425
        return False
352
426
 
353
427
 
 
428
def is_inside_or_parent_of_any(dir_list, fname):
 
429
    """True if fname is a child or a parent of any of the given files."""
 
430
    for dirname in dir_list:
 
431
        if is_inside(dirname, fname) or is_inside(fname, dirname):
 
432
            return True
 
433
    else:
 
434
        return False
 
435
 
 
436
 
354
437
def pumpfile(fromfile, tofile):
355
438
    """Copy contents of one file to another."""
356
439
    BUFSIZE = 32768
629
712
    assert len(base) >= MIN_ABS_PATHLENGTH, ('Length of base must be equal or'
630
713
        ' exceed the platform minimum length (which is %d)' % 
631
714
        MIN_ABS_PATHLENGTH)
 
715
 
632
716
    rp = abspath(path)
633
717
 
634
718
    s = []
640
724
        if tail:
641
725
            s.insert(0, tail)
642
726
    else:
643
 
        # XXX This should raise a NotChildPath exception, as its not tied
644
 
        # to branch anymore.
645
727
        raise PathNotChild(rp, base)
646
728
 
647
729
    if s:
666
748
        raise BzrBadParameterNotUnicode(unicode_or_utf8_string)
667
749
 
668
750
 
 
751
_platform_normalizes_filenames = False
 
752
if sys.platform == 'darwin':
 
753
    _platform_normalizes_filenames = True
 
754
 
 
755
 
 
756
def normalizes_filenames():
 
757
    """Return True if this platform normalizes unicode filenames.
 
758
 
 
759
    Mac OSX does, Windows/Linux do not.
 
760
    """
 
761
    return _platform_normalizes_filenames
 
762
 
 
763
 
 
764
if _platform_normalizes_filenames:
 
765
    def unicode_filename(path):
 
766
        """Make sure 'path' is a properly normalized filename.
 
767
 
 
768
        On platforms where the system normalizes filenames (Mac OSX),
 
769
        you can access a file by any path which will normalize
 
770
        correctly.
 
771
        Internally, bzr only supports NFC/NFKC normalization, since
 
772
        that is the standard for XML documents.
 
773
        So we return an normalized path, and indicate this has been
 
774
        properly normalized.
 
775
 
 
776
        :return: (path, is_normalized) Return a path which can
 
777
                access the file, and whether or not this path is
 
778
                normalized.
 
779
        """
 
780
        return unicodedata.normalize('NFKC', path), True
 
781
else:
 
782
    def unicode_filename(path):
 
783
        """Make sure 'path' is a properly normalized filename.
 
784
 
 
785
        On platforms where the system does not normalize filenames 
 
786
        (Windows, Linux), you have to access a file by its exact path.
 
787
        Internally, bzr only supports NFC/NFKC normalization, since
 
788
        that is the standard for XML documents.
 
789
        So we return the original path, and indicate if this is
 
790
        properly normalized.
 
791
 
 
792
        :return: (path, is_normalized) Return a path which can
 
793
                access the file, and whether or not this path is
 
794
                normalized.
 
795
        """
 
796
        return path, unicodedata.normalize('NFKC', path) == path
 
797
 
 
798
 
669
799
def terminal_width():
670
800
    """Return estimated terminal width."""
671
801
    if sys.platform == 'win32':
693
823
    return sys.platform != "win32"
694
824
 
695
825
 
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
826
_validWin32PathRE = re.compile(r'^([A-Za-z]:[/\\])?[^:<>*"?\|]*$')
707
827
 
708
828
 
715
835
        return
716
836
    if _validWin32PathRE.match(path) is None:
717
837
        raise IllegalPath(path)
 
838
 
 
839
 
 
840
def walkdirs(top, prefix=""):
 
841
    """Yield data about all the directories in a tree.
 
842
    
 
843
    This yields all the data about the contents of a directory at a time.
 
844
    After each directory has been yielded, if the caller has mutated the list
 
845
    to exclude some directories, they are then not descended into.
 
846
    
 
847
    The data yielded is of the form:
 
848
    [(relpath, basename, kind, lstat, path_from_top), ...]
 
849
 
 
850
    :param prefix: Prefix the relpaths that are yielded with 'prefix'. This 
 
851
        allows one to walk a subtree but get paths that are relative to a tree
 
852
        rooted higher up.
 
853
    :return: an iterator over the dirs.
 
854
    """
 
855
    lstat = os.lstat
 
856
    pending = []
 
857
    _directory = _directory_kind
 
858
    _listdir = listdir
 
859
    pending = [(prefix, "", _directory, None, top)]
 
860
    while pending:
 
861
        dirblock = []
 
862
        currentdir = pending.pop()
 
863
        # 0 - relpath, 1- basename, 2- kind, 3- stat, 4-toppath
 
864
        top = currentdir[4]
 
865
        if currentdir[0]:
 
866
            relroot = currentdir[0] + '/'
 
867
        else:
 
868
            relroot = ""
 
869
        for name in sorted(_listdir(top)):
 
870
            abspath = top + '/' + name
 
871
            statvalue = lstat(abspath)
 
872
            dirblock.append ((relroot + name, name, file_kind_from_stat_mode(statvalue.st_mode), statvalue, abspath))
 
873
        yield dirblock
 
874
        # push the user specified dirs from dirblock
 
875
        for dir in reversed(dirblock):
 
876
            if dir[2] == _directory:
 
877
                pending.append(dir)
 
878
 
 
879
 
 
880
def path_prefix_key(path):
 
881
    """Generate a prefix-order path key for path.
 
882
 
 
883
    This can be used to sort paths in the same way that walkdirs does.
 
884
    """
 
885
    return (dirname(path) , path)
 
886
 
 
887
 
 
888
def compare_paths_prefix_order(path_a, path_b):
 
889
    """Compare path_a and path_b to generate the same order walkdirs uses."""
 
890
    key_a = path_prefix_key(path_a)
 
891
    key_b = path_prefix_key(path_b)
 
892
    return cmp(key_a, key_b)