~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/osutils.py

first cut at merge from integration.

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)
19
22
from cStringIO import StringIO
20
23
import errno
21
24
import os
22
 
from os import listdir
23
25
import re
24
26
import sha
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)
30
27
import string
31
28
import sys
32
29
import time
33
30
import types
34
31
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
 
                    )
41
32
 
42
33
import bzrlib
43
34
from bzrlib.errors import (BzrError,
44
35
                           BzrBadParameterNotUnicode,
45
36
                           NoSuchFile,
46
37
                           PathNotChild,
47
 
                           IllegalPath,
48
38
                           )
49
 
from bzrlib.symbol_versioning import *
50
39
from bzrlib.trace import mutter
51
 
import bzrlib.win32console
52
40
 
53
41
 
54
42
def make_readonly(filename):
83
71
        return f
84
72
 
85
73
 
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
 
    try:
112
 
        return _mapper(_lstat(f).st_mode)
113
 
    except OSError, e:
114
 
        if getattr(e, 'errno', None) == errno.ENOENT:
115
 
            raise bzrlib.errors.NoSuchFile(f)
116
 
        raise
 
74
def file_kind(f):
 
75
    mode = os.lstat(f)[ST_MODE]
 
76
    if S_ISREG(mode):
 
77
        return 'file'
 
78
    elif S_ISDIR(mode):
 
79
        return 'directory'
 
80
    elif S_ISLNK(mode):
 
81
        return 'symlink'
 
82
    elif S_ISCHR(mode):
 
83
        return 'chardev'
 
84
    elif S_ISBLK(mode):
 
85
        return 'block'
 
86
    elif S_ISFIFO(mode):
 
87
        return 'fifo'
 
88
    elif S_ISSOCK(mode):
 
89
        return 'socket'
 
90
    else:
 
91
        return 'unknown'
117
92
 
118
93
 
119
94
def kind_marker(kind):
120
95
    if kind == 'file':
121
96
        return ''
122
 
    elif kind == _directory_kind:
 
97
    elif kind == 'directory':
123
98
        return '/'
124
99
    elif kind == 'symlink':
125
100
        return '@'
126
101
    else:
127
102
        raise BzrError('invalid file kind %r' % kind)
128
103
 
129
 
lexists = getattr(os.path, 'lexists', None)
130
 
if lexists is None:
131
 
    def lexists(f):
132
 
        try:
133
 
            if hasattr(os, 'lstat'):
134
 
                os.lstat(f)
135
 
            else:
136
 
                os.stat(f)
137
 
            return True
138
 
        except OSError,e:
139
 
            if e.errno == errno.ENOENT:
140
 
                return False;
141
 
            else:
142
 
                raise BzrError("lstat/stat of (%r): %r" % (f, e))
143
 
 
 
104
def lexists(f):
 
105
    if hasattr(os.path, 'lexists'):
 
106
        return os.path.lexists(f)
 
107
    try:
 
108
        if hasattr(os, 'lstat'):
 
109
            os.lstat(f)
 
110
        else:
 
111
            os.stat(f)
 
112
        return True
 
113
    except OSError,e:
 
114
        if e.errno == errno.ENOENT:
 
115
            return False;
 
116
        else:
 
117
            raise BzrError("lstat/stat of (%r): %r" % (f, e))
144
118
 
145
119
def fancy_rename(old, new, rename_func, unlink_func):
146
120
    """A fancy rename, when you don't have atomic rename.
155
129
    import random
156
130
    base = os.path.basename(new)
157
131
    dirname = os.path.dirname(new)
158
 
    tmp_name = u'tmp.%s.%.9f.%d.%s' % (base, time.time(), os.getpid(), rand_chars(10))
 
132
    tmp_name = u'tmp.%s.%.9f.%d.%d' % (base, time.time(), os.getpid(), random.randint(0, 0x7FFFFFFF))
159
133
    tmp_name = pathjoin(dirname, tmp_name)
160
134
 
161
135
    # Rename the file out of the way, but keep track if it didn't exist
196
170
            else:
197
171
                rename_func(tmp_name, new)
198
172
 
199
 
 
200
 
# In Python 2.4.2 and older, os.path.abspath and os.path.realpath
201
 
# choke on a Unicode string containing a relative path if
202
 
# os.getcwd() returns a non-sys.getdefaultencoding()-encoded
203
 
# string.
204
 
_fs_enc = sys.getfilesystemencoding()
205
 
def _posix_abspath(path):
206
 
    return os.path.abspath(path.encode(_fs_enc)).decode(_fs_enc)
207
 
    # jam 20060426 This is another possibility which mimics 
208
 
    # os.path.abspath, only uses unicode characters instead
209
 
    # if not os.path.isabs(path):
210
 
    #     return os.path.join(os.getcwdu(), path)
211
 
    # return path
212
 
 
213
 
 
214
 
def _posix_realpath(path):
215
 
    return os.path.realpath(path.encode(_fs_enc)).decode(_fs_enc)
216
 
 
217
 
 
218
 
def _win32_abspath(path):
219
 
    return _nt_abspath(path.encode(_fs_enc)).decode(_fs_enc).replace('\\', '/')
220
 
 
221
 
 
222
 
def _win32_realpath(path):
223
 
    return _nt_realpath(path.encode(_fs_enc)).decode(_fs_enc).replace('\\', '/')
224
 
 
225
 
 
226
 
def _win32_pathjoin(*args):
227
 
    return _nt_join(*args).replace('\\', '/')
228
 
 
229
 
 
230
 
def _win32_normpath(path):
231
 
    return _nt_normpath(path).replace('\\', '/')
232
 
 
233
 
 
234
 
def _win32_getcwd():
235
 
    return os.getcwdu().replace('\\', '/')
236
 
 
237
 
 
238
 
def _win32_mkdtemp(*args, **kwargs):
239
 
    return tempfile.mkdtemp(*args, **kwargs).replace('\\', '/')
240
 
 
241
 
 
242
 
def _win32_rename(old, new):
243
 
    fancy_rename(old, new, rename_func=os.rename, unlink_func=os.unlink)
244
 
 
245
 
 
246
 
# Default is to just use the python builtins, but these can be rebound on
247
 
# particular platforms.
248
 
abspath = _posix_abspath
249
 
realpath = _posix_realpath
 
173
# Default is to just use the python builtins
 
174
abspath = os.path.abspath
 
175
realpath = os.path.realpath
250
176
pathjoin = os.path.join
251
177
normpath = os.path.normpath
252
178
getcwd = os.getcwdu
254
180
rename = os.rename
255
181
dirname = os.path.dirname
256
182
basename = os.path.basename
257
 
rmtree = shutil.rmtree
258
 
 
259
 
MIN_ABS_PATHLENGTH = 1
260
 
 
 
183
 
 
184
if os.name == "posix":
 
185
    # In Python 2.4.2 and older, os.path.abspath and os.path.realpath
 
186
    # choke on a Unicode string containing a relative path if
 
187
    # os.getcwd() returns a non-sys.getdefaultencoding()-encoded
 
188
    # string.
 
189
    _fs_enc = sys.getfilesystemencoding()
 
190
    def abspath(path):
 
191
        return os.path.abspath(path.encode(_fs_enc)).decode(_fs_enc)
 
192
 
 
193
    def realpath(path):
 
194
        return os.path.realpath(path.encode(_fs_enc)).decode(_fs_enc)
261
195
 
262
196
if sys.platform == 'win32':
263
 
    abspath = _win32_abspath
264
 
    realpath = _win32_realpath
265
 
    pathjoin = _win32_pathjoin
266
 
    normpath = _win32_normpath
267
 
    getcwd = _win32_getcwd
268
 
    mkdtemp = _win32_mkdtemp
269
 
    rename = _win32_rename
270
 
 
271
 
    MIN_ABS_PATHLENGTH = 3
272
 
 
273
 
    def _win32_delete_readonly(function, path, excinfo):
274
 
        """Error handler for shutil.rmtree function [for win32]
275
 
        Helps to remove files and dirs marked as read-only.
276
 
        """
277
 
        type_, value = excinfo[:2]
278
 
        if function in (os.remove, os.rmdir) \
279
 
            and type_ == OSError \
280
 
            and value.errno == errno.EACCES:
281
 
            bzrlib.osutils.make_writable(path)
282
 
            function(path)
283
 
        else:
284
 
            raise
285
 
 
286
 
    def rmtree(path, ignore_errors=False, onerror=_win32_delete_readonly):
287
 
        """Replacer for shutil.rmtree: could remove readonly dirs/files"""
288
 
        return shutil.rmtree(path, ignore_errors, onerror)
 
197
    # We need to use the Unicode-aware os.path.abspath and
 
198
    # os.path.realpath on Windows systems.
 
199
    def abspath(path):
 
200
        return os.path.abspath(path).replace('\\', '/')
 
201
 
 
202
    def realpath(path):
 
203
        return os.path.realpath(path).replace('\\', '/')
 
204
 
 
205
    def pathjoin(*args):
 
206
        return os.path.join(*args).replace('\\', '/')
 
207
 
 
208
    def normpath(path):
 
209
        return os.path.normpath(path).replace('\\', '/')
 
210
 
 
211
    def getcwd():
 
212
        return os.getcwdu().replace('\\', '/')
 
213
 
 
214
    def mkdtemp(*args, **kwargs):
 
215
        return tempfile.mkdtemp(*args, **kwargs).replace('\\', '/')
 
216
 
 
217
    def rename(old, new):
 
218
        fancy_rename(old, new, rename_func=os.rename, unlink_func=os.unlink)
289
219
 
290
220
 
291
221
def normalizepath(f):
396
326
        return False
397
327
 
398
328
 
399
 
def is_inside_or_parent_of_any(dir_list, fname):
400
 
    """True if fname is a child or a parent of any of the given files."""
401
 
    for dirname in dir_list:
402
 
        if is_inside(dirname, fname) or is_inside(fname, dirname):
403
 
            return True
404
 
    else:
405
 
        return False
406
 
 
407
 
 
408
329
def pumpfile(fromfile, tofile):
409
330
    """Copy contents of one file to another."""
410
331
    BUFSIZE = 32768
520
441
    """Return size of given open file."""
521
442
    return os.fstat(f.fileno())[ST_SIZE]
522
443
 
523
 
 
524
444
# Define rand_bytes based on platform.
525
445
try:
526
446
    # Python 2.4 and later have os.urandom,
543
463
                n -= 1
544
464
            return s
545
465
 
546
 
 
547
 
ALNUM = '0123456789abcdefghijklmnopqrstuvwxyz'
548
 
def rand_chars(num):
549
 
    """Return a random string of num alphanumeric characters
550
 
    
551
 
    The result only contains lowercase chars because it may be used on 
552
 
    case-insensitive filesystems.
553
 
    """
554
 
    s = ''
555
 
    for raw_byte in rand_bytes(num):
556
 
        s += ALNUM[ord(raw_byte) % 36]
557
 
    return s
558
 
 
559
 
 
560
466
## TODO: We could later have path objects that remember their list
561
467
## decomposition (might be too tricksy though.)
562
468
 
600
506
    return pathjoin(*p)
601
507
 
602
508
 
603
 
@deprecated_function(zero_nine)
604
509
def appendpath(p1, p2):
605
510
    if p1 == '':
606
511
        return p2
610
515
 
611
516
def split_lines(s):
612
517
    """Split s into lines, but without removing the newline characters."""
613
 
    lines = s.split('\n')
614
 
    result = [line + '\n' for line in lines[:-1]]
615
 
    if lines[-1]:
616
 
        result.append(lines[-1])
617
 
    return result
 
518
    return StringIO(s).readlines()
618
519
 
619
520
 
620
521
def hardlinks_good():
633
534
            raise
634
535
        copyfile(src, dest)
635
536
 
636
 
def delete_any(full_path):
637
 
    """Delete a file or directory."""
638
 
    try:
639
 
        os.unlink(full_path)
640
 
    except OSError, e:
641
 
    # We may be renaming a dangling inventory id
642
 
        if e.errno not in (errno.EISDIR, errno.EACCES, errno.EPERM):
643
 
            raise
644
 
        os.rmdir(full_path)
645
 
 
646
537
 
647
538
def has_symlinks():
648
539
    if hasattr(os, 'symlink'):
677
568
 
678
569
    os.path.commonprefix (python2.4) has a bad bug that it works just
679
570
    on string prefixes, assuming that '/u' is a prefix of '/u2'.  This
680
 
    avoids that problem.
681
 
    """
682
 
 
683
 
    assert len(base) >= MIN_ABS_PATHLENGTH, ('Length of base must be equal or'
684
 
        ' exceed the platform minimum length (which is %d)' % 
685
 
        MIN_ABS_PATHLENGTH)
686
 
 
 
571
    avoids that problem."""
687
572
    rp = abspath(path)
688
573
 
689
574
    s = []
695
580
        if tail:
696
581
            s.insert(0, tail)
697
582
    else:
 
583
        # XXX This should raise a NotChildPath exception, as its not tied
 
584
        # to branch anymore.
698
585
        raise PathNotChild(rp, base)
699
586
 
700
587
    if s:
719
606
        raise BzrBadParameterNotUnicode(unicode_or_utf8_string)
720
607
 
721
608
 
722
 
_platform_normalizes_filenames = False
723
 
if sys.platform == 'darwin':
724
 
    _platform_normalizes_filenames = True
725
 
 
726
 
 
727
 
def normalizes_filenames():
728
 
    """Return True if this platform normalizes unicode filenames.
729
 
 
730
 
    Mac OSX does, Windows/Linux do not.
731
 
    """
732
 
    return _platform_normalizes_filenames
733
 
 
734
 
 
735
 
if _platform_normalizes_filenames:
736
 
    def unicode_filename(path):
737
 
        """Make sure 'path' is a properly normalized filename.
738
 
 
739
 
        On platforms where the system normalizes filenames (Mac OSX),
740
 
        you can access a file by any path which will normalize
741
 
        correctly.
742
 
        Internally, bzr only supports NFC/NFKC normalization, since
743
 
        that is the standard for XML documents.
744
 
        So we return an normalized path, and indicate this has been
745
 
        properly normalized.
746
 
 
747
 
        :return: (path, is_normalized) Return a path which can
748
 
                access the file, and whether or not this path is
749
 
                normalized.
750
 
        """
751
 
        return unicodedata.normalize('NFKC', path), True
752
 
else:
753
 
    def unicode_filename(path):
754
 
        """Make sure 'path' is a properly normalized filename.
755
 
 
756
 
        On platforms where the system does not normalize filenames 
757
 
        (Windows, Linux), you have to access a file by its exact path.
758
 
        Internally, bzr only supports NFC/NFKC normalization, since
759
 
        that is the standard for XML documents.
760
 
        So we return the original path, and indicate if this is
761
 
        properly normalized.
762
 
 
763
 
        :return: (path, is_normalized) Return a path which can
764
 
                access the file, and whether or not this path is
765
 
                normalized.
766
 
        """
767
 
        return path, unicodedata.normalize('NFKC', path) == path
768
 
 
769
 
 
770
609
def terminal_width():
771
610
    """Return estimated terminal width."""
772
 
    if sys.platform == 'win32':
773
 
        import bzrlib.win32console
774
 
        return bzrlib.win32console.get_console_size()[0]
775
 
    width = 0
 
611
 
 
612
    # TODO: Do something smart on Windows?
 
613
 
 
614
    # TODO: Is there anything that gets a better update when the window
 
615
    # is resized while the program is running? We could use the Python termcap
 
616
    # library.
776
617
    try:
777
 
        import struct, fcntl, termios
778
 
        s = struct.pack('HHHH', 0, 0, 0, 0)
779
 
        x = fcntl.ioctl(1, termios.TIOCGWINSZ, s)
780
 
        width = struct.unpack('HHHH', x)[1]
781
 
    except IOError:
782
 
        pass
783
 
    if width <= 0:
784
 
        try:
785
 
            width = int(os.environ['COLUMNS'])
786
 
        except:
787
 
            pass
788
 
    if width <= 0:
789
 
        width = 80
790
 
 
791
 
    return width
792
 
 
793
 
def supports_executable():
794
 
    return sys.platform != "win32"
795
 
 
796
 
 
797
 
_validWin32PathRE = re.compile(r'^([A-Za-z]:[/\\])?[^:<>*"?\|]*$')
798
 
 
799
 
 
800
 
def check_legal_path(path):
801
 
    """Check whether the supplied path is legal.  
802
 
    This is only required on Windows, so we don't test on other platforms
803
 
    right now.
804
 
    """
805
 
    if sys.platform != "win32":
806
 
        return
807
 
    if _validWin32PathRE.match(path) is None:
808
 
        raise IllegalPath(path)
809
 
 
810
 
 
811
 
def walkdirs(top, prefix=""):
812
 
    """Yield data about all the directories in a tree.
813
 
    
814
 
    This yields all the data about the contents of a directory at a time.
815
 
    After each directory has been yielded, if the caller has mutated the list
816
 
    to exclude some directories, they are then not descended into.
817
 
    
818
 
    The data yielded is of the form:
819
 
    [(relpath, basename, kind, lstat, path_from_top), ...]
820
 
 
821
 
    :param prefix: Prefix the relpaths that are yielded with 'prefix'. This 
822
 
        allows one to walk a subtree but get paths that are relative to a tree
823
 
        rooted higher up.
824
 
    :return: an iterator over the dirs.
825
 
    """
826
 
    lstat = os.lstat
827
 
    pending = []
828
 
    _directory = _directory_kind
829
 
    _listdir = listdir
830
 
    pending = [(prefix, "", _directory, None, top)]
831
 
    while pending:
832
 
        dirblock = []
833
 
        currentdir = pending.pop()
834
 
        # 0 - relpath, 1- basename, 2- kind, 3- stat, 4-toppath
835
 
        top = currentdir[4]
836
 
        if currentdir[0]:
837
 
            relroot = currentdir[0] + '/'
838
 
        else:
839
 
            relroot = ""
840
 
        for name in sorted(_listdir(top)):
841
 
            abspath = top + '/' + name
842
 
            statvalue = lstat(abspath)
843
 
            dirblock.append ((relroot + name, name, file_kind_from_stat_mode(statvalue.st_mode), statvalue, abspath))
844
 
        yield dirblock
845
 
        # push the user specified dirs from dirblock
846
 
        for dir in reversed(dirblock):
847
 
            if dir[2] == _directory:
848
 
                pending.append(dir)
 
618
        return int(os.environ['COLUMNS'])
 
619
    except (IndexError, KeyError, ValueError):
 
620
        return 80