~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/osutils.py

  • Committer: Jelmer Vernooij
  • Date: 2006-06-13 13:24:40 UTC
  • mfrom: (1767 +trunk)
  • mto: (1769.1.1 integration)
  • mto: This revision was merged to the branch mainline in revision 1770.
  • Revision ID: jelmer@samba.org-20060613132440-24e222a86f948f60
[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
24
21
import os
 
22
from os import listdir
25
23
import re
26
24
import sha
27
25
import shutil
 
26
from shutil import copyfile
 
27
import stat
 
28
from stat import (S_ISREG, S_ISDIR, S_ISLNK, ST_MODE, ST_SIZE,
 
29
                  S_ISCHR, S_ISBLK, S_ISFIFO, S_ISSOCK)
28
30
import string
29
31
import sys
30
32
import time
31
33
import types
32
34
import tempfile
 
35
import unicodedata
 
36
from ntpath import (abspath as _nt_abspath,
 
37
                    join as _nt_join,
 
38
                    normpath as _nt_normpath,
 
39
                    realpath as _nt_realpath,
 
40
                    )
33
41
 
34
42
import bzrlib
35
43
from bzrlib.errors import (BzrError,
38
46
                           PathNotChild,
39
47
                           IllegalPath,
40
48
                           )
 
49
from bzrlib.symbol_versioning import *
41
50
from bzrlib.trace import mutter
42
51
import bzrlib.win32console
43
52
 
74
83
        return f
75
84
 
76
85
 
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'
 
86
_directory_kind = 'directory'
 
87
 
 
88
_formats = {
 
89
    stat.S_IFDIR:_directory_kind,
 
90
    stat.S_IFCHR:'chardev',
 
91
    stat.S_IFBLK:'block',
 
92
    stat.S_IFREG:'file',
 
93
    stat.S_IFIFO:'fifo',
 
94
    stat.S_IFLNK:'symlink',
 
95
    stat.S_IFSOCK:'socket',
 
96
}
 
97
 
 
98
 
 
99
def file_kind_from_stat_mode(stat_mode, _formats=_formats, _unknown='unknown'):
 
100
    """Generate a file kind from a stat mode. This is used in walkdirs.
 
101
 
 
102
    Its performance is critical: Do not mutate without careful benchmarking.
 
103
    """
 
104
    try:
 
105
        return _formats[stat_mode & 0170000]
 
106
    except KeyError:
 
107
        return _unknown
 
108
 
 
109
 
 
110
def file_kind(f, _lstat=os.lstat, _mapper=file_kind_from_stat_mode):
 
111
    return _mapper(_lstat(f).st_mode)
95
112
 
96
113
 
97
114
def kind_marker(kind):
98
115
    if kind == 'file':
99
116
        return ''
100
 
    elif kind == 'directory':
 
117
    elif kind == _directory_kind:
101
118
        return '/'
102
119
    elif kind == 'symlink':
103
120
        return '@'
104
121
    else:
105
122
        raise BzrError('invalid file kind %r' % kind)
106
123
 
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))
 
124
lexists = getattr(os.path, 'lexists', None)
 
125
if lexists is None:
 
126
    def lexists(f):
 
127
        try:
 
128
            if hasattr(os, 'lstat'):
 
129
                os.lstat(f)
 
130
            else:
 
131
                os.stat(f)
 
132
            return True
 
133
        except OSError,e:
 
134
            if e.errno == errno.ENOENT:
 
135
                return False;
 
136
            else:
 
137
                raise BzrError("lstat/stat of (%r): %r" % (f, e))
 
138
 
121
139
 
122
140
def fancy_rename(old, new, rename_func, unlink_func):
123
141
    """A fancy rename, when you don't have atomic rename.
173
191
            else:
174
192
                rename_func(tmp_name, new)
175
193
 
 
194
 
 
195
# In Python 2.4.2 and older, os.path.abspath and os.path.realpath
 
196
# choke on a Unicode string containing a relative path if
 
197
# os.getcwd() returns a non-sys.getdefaultencoding()-encoded
 
198
# string.
 
199
_fs_enc = sys.getfilesystemencoding()
 
200
def _posix_abspath(path):
 
201
    return os.path.abspath(path.encode(_fs_enc)).decode(_fs_enc)
 
202
    # jam 20060426 This is another possibility which mimics 
 
203
    # os.path.abspath, only uses unicode characters instead
 
204
    # if not os.path.isabs(path):
 
205
    #     return os.path.join(os.getcwdu(), path)
 
206
    # return path
 
207
 
 
208
 
 
209
def _posix_realpath(path):
 
210
    return os.path.realpath(path.encode(_fs_enc)).decode(_fs_enc)
 
211
 
 
212
 
 
213
def _win32_abspath(path):
 
214
    return _nt_abspath(path.encode(_fs_enc)).decode(_fs_enc).replace('\\', '/')
 
215
 
 
216
 
 
217
def _win32_realpath(path):
 
218
    return _nt_realpath(path.encode(_fs_enc)).decode(_fs_enc).replace('\\', '/')
 
219
 
 
220
 
 
221
def _win32_pathjoin(*args):
 
222
    return _nt_join(*args).replace('\\', '/')
 
223
 
 
224
 
 
225
def _win32_normpath(path):
 
226
    return _nt_normpath(path).replace('\\', '/')
 
227
 
 
228
 
 
229
def _win32_getcwd():
 
230
    return os.getcwdu().replace('\\', '/')
 
231
 
 
232
 
 
233
def _win32_mkdtemp(*args, **kwargs):
 
234
    return tempfile.mkdtemp(*args, **kwargs).replace('\\', '/')
 
235
 
 
236
 
 
237
def _win32_rename(old, new):
 
238
    fancy_rename(old, new, rename_func=os.rename, unlink_func=os.unlink)
 
239
 
 
240
 
176
241
# Default is to just use the python builtins, but these can be rebound on
177
242
# particular platforms.
178
 
abspath = os.path.abspath
179
 
realpath = os.path.realpath
 
243
abspath = _posix_abspath
 
244
realpath = _posix_realpath
180
245
pathjoin = os.path.join
181
246
normpath = os.path.normpath
182
247
getcwd = os.getcwdu
188
253
 
189
254
MIN_ABS_PATHLENGTH = 1
190
255
 
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
256
 
203
257
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)
 
258
    abspath = _win32_abspath
 
259
    realpath = _win32_realpath
 
260
    pathjoin = _win32_pathjoin
 
261
    normpath = _win32_normpath
 
262
    getcwd = _win32_getcwd
 
263
    mkdtemp = _win32_mkdtemp
 
264
    rename = _win32_rename
226
265
 
227
266
    MIN_ABS_PATHLENGTH = 3
228
267
 
352
391
        return False
353
392
 
354
393
 
 
394
def is_inside_or_parent_of_any(dir_list, fname):
 
395
    """True if fname is a child or a parent of any of the given files."""
 
396
    for dirname in dir_list:
 
397
        if is_inside(dirname, fname) or is_inside(fname, dirname):
 
398
            return True
 
399
    else:
 
400
        return False
 
401
 
 
402
 
355
403
def pumpfile(fromfile, tofile):
356
404
    """Copy contents of one file to another."""
357
405
    BUFSIZE = 32768
547
595
    return pathjoin(*p)
548
596
 
549
597
 
 
598
@deprecated_function(zero_nine)
550
599
def appendpath(p1, p2):
551
600
    if p1 == '':
552
601
        return p2
629
678
    assert len(base) >= MIN_ABS_PATHLENGTH, ('Length of base must be equal or'
630
679
        ' exceed the platform minimum length (which is %d)' % 
631
680
        MIN_ABS_PATHLENGTH)
 
681
 
632
682
    rp = abspath(path)
633
683
 
634
684
    s = []
640
690
        if tail:
641
691
            s.insert(0, tail)
642
692
    else:
643
 
        # XXX This should raise a NotChildPath exception, as its not tied
644
 
        # to branch anymore.
645
693
        raise PathNotChild(rp, base)
646
694
 
647
695
    if s:
666
714
        raise BzrBadParameterNotUnicode(unicode_or_utf8_string)
667
715
 
668
716
 
 
717
_platform_normalizes_filenames = False
 
718
if sys.platform == 'darwin':
 
719
    _platform_normalizes_filenames = True
 
720
 
 
721
 
 
722
def normalizes_filenames():
 
723
    """Return True if this platform normalizes unicode filenames.
 
724
 
 
725
    Mac OSX does, Windows/Linux do not.
 
726
    """
 
727
    return _platform_normalizes_filenames
 
728
 
 
729
 
 
730
if _platform_normalizes_filenames:
 
731
    def unicode_filename(path):
 
732
        """Make sure 'path' is a properly normalized filename.
 
733
 
 
734
        On platforms where the system normalizes filenames (Mac OSX),
 
735
        you can access a file by any path which will normalize
 
736
        correctly.
 
737
        Internally, bzr only supports NFC/NFKC normalization, since
 
738
        that is the standard for XML documents.
 
739
        So we return an normalized path, and indicate this has been
 
740
        properly normalized.
 
741
 
 
742
        :return: (path, is_normalized) Return a path which can
 
743
                access the file, and whether or not this path is
 
744
                normalized.
 
745
        """
 
746
        return unicodedata.normalize('NFKC', path), True
 
747
else:
 
748
    def unicode_filename(path):
 
749
        """Make sure 'path' is a properly normalized filename.
 
750
 
 
751
        On platforms where the system does not normalize filenames 
 
752
        (Windows, Linux), you have to access a file by its exact path.
 
753
        Internally, bzr only supports NFC/NFKC normalization, since
 
754
        that is the standard for XML documents.
 
755
        So we return the original path, and indicate if this is
 
756
        properly normalized.
 
757
 
 
758
        :return: (path, is_normalized) Return a path which can
 
759
                access the file, and whether or not this path is
 
760
                normalized.
 
761
        """
 
762
        return path, unicodedata.normalize('NFKC', path) == path
 
763
 
 
764
 
669
765
def terminal_width():
670
766
    """Return estimated terminal width."""
671
767
    if sys.platform == 'win32':
693
789
    return sys.platform != "win32"
694
790
 
695
791
 
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
792
_validWin32PathRE = re.compile(r'^([A-Za-z]:[/\\])?[^:<>*"?\|]*$')
707
793
 
708
794
 
715
801
        return
716
802
    if _validWin32PathRE.match(path) is None:
717
803
        raise IllegalPath(path)
 
804
 
 
805
 
 
806
def walkdirs(top):
 
807
    """Yield data about all the directories in a tree.
 
808
    
 
809
    This yields all the data about the contents of a directory at a time.
 
810
    After each directory has been yielded, if the caller has mutated the list
 
811
    to exclude some directories, they are then not descended into.
 
812
    
 
813
    The data yielded is of the form:
 
814
    [(relpath, basename, kind, lstat, path_from_top), ...]
 
815
 
 
816
    :return: an iterator over the dirs.
 
817
    """
 
818
    lstat = os.lstat
 
819
    pending = []
 
820
    _directory = _directory_kind
 
821
    _listdir = listdir
 
822
    pending = [("", "", _directory, None, top)]
 
823
    while pending:
 
824
        dirblock = []
 
825
        currentdir = pending.pop()
 
826
        # 0 - relpath, 1- basename, 2- kind, 3- stat, 4-toppath
 
827
        top = currentdir[4]
 
828
        if currentdir[0]:
 
829
            relroot = currentdir[0] + '/'
 
830
        else:
 
831
            relroot = ""
 
832
        for name in sorted(_listdir(top)):
 
833
            abspath = top + '/' + name
 
834
            statvalue = lstat(abspath)
 
835
            dirblock.append ((relroot + name, name, file_kind_from_stat_mode(statvalue.st_mode), statvalue, abspath))
 
836
        yield dirblock
 
837
        # push the user specified dirs from dirblock
 
838
        for dir in reversed(dirblock):
 
839
            if dir[2] == _directory:
 
840
                pending.append(dir)