~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/osutils.py

- rules for using destructors

Show diffs side-by-side

added added

removed removed

Lines of Context:
24
24
import os
25
25
import re
26
26
import sha
27
 
import shutil
28
27
import string
29
28
import sys
30
29
import time
31
30
import types
32
 
import tempfile
33
31
 
34
32
import bzrlib
35
 
from bzrlib.errors import (BzrError,
36
 
                           BzrBadParameterNotUnicode,
37
 
                           NoSuchFile,
38
 
                           PathNotChild,
39
 
                           IllegalPath,
40
 
                           )
 
33
from bzrlib.errors import BzrError, NotBranchError
41
34
from bzrlib.trace import mutter
42
35
 
43
36
 
104
97
        raise BzrError('invalid file kind %r' % kind)
105
98
 
106
99
def lexists(f):
107
 
    if hasattr(os.path, 'lexists'):
108
 
        return os.path.lexists(f)
109
100
    try:
110
101
        if hasattr(os, 'lstat'):
111
102
            os.lstat(f)
118
109
        else:
119
110
            raise BzrError("lstat/stat of (%r): %r" % (f, e))
120
111
 
121
 
def fancy_rename(old, new, rename_func, unlink_func):
122
 
    """A fancy rename, when you don't have atomic rename.
123
 
    
124
 
    :param old: The old path, to rename from
125
 
    :param new: The new path, to rename to
126
 
    :param rename_func: The potentially non-atomic rename function
127
 
    :param unlink_func: A way to delete the target file if the full rename succeeds
128
 
    """
129
 
 
130
 
    # sftp rename doesn't allow overwriting, so play tricks:
131
 
    import random
132
 
    base = os.path.basename(new)
133
 
    dirname = os.path.dirname(new)
134
 
    tmp_name = u'tmp.%s.%.9f.%d.%s' % (base, time.time(), os.getpid(), rand_chars(10))
135
 
    tmp_name = pathjoin(dirname, tmp_name)
136
 
 
137
 
    # Rename the file out of the way, but keep track if it didn't exist
138
 
    # We don't want to grab just any exception
139
 
    # something like EACCES should prevent us from continuing
140
 
    # The downside is that the rename_func has to throw an exception
141
 
    # with an errno = ENOENT, or NoSuchFile
142
 
    file_existed = False
143
 
    try:
144
 
        rename_func(new, tmp_name)
145
 
    except (NoSuchFile,), e:
146
 
        pass
147
 
    except IOError, e:
148
 
        # RBC 20060103 abstraction leakage: the paramiko SFTP clients rename
149
 
        # function raises an IOError with errno == None when a rename fails.
150
 
        # This then gets caught here.
151
 
        if e.errno not in (None, errno.ENOENT, errno.ENOTDIR):
152
 
            raise
153
 
    except Exception, e:
154
 
        if (not hasattr(e, 'errno') 
155
 
            or e.errno not in (errno.ENOENT, errno.ENOTDIR)):
156
 
            raise
157
 
    else:
158
 
        file_existed = True
159
 
 
160
 
    success = False
161
 
    try:
162
 
        # This may throw an exception, in which case success will
163
 
        # not be set.
164
 
        rename_func(old, new)
165
 
        success = True
166
 
    finally:
167
 
        if file_existed:
168
 
            # If the file used to exist, rename it back into place
169
 
            # otherwise just delete it from the tmp location
170
 
            if success:
171
 
                unlink_func(tmp_name)
172
 
            else:
173
 
                rename_func(tmp_name, new)
174
 
 
175
 
# Default is to just use the python builtins, but these can be rebound on
176
 
# particular platforms.
177
 
abspath = os.path.abspath
178
 
realpath = os.path.realpath
179
 
pathjoin = os.path.join
180
 
normpath = os.path.normpath
181
 
getcwd = os.getcwdu
182
 
mkdtemp = tempfile.mkdtemp
183
 
rename = os.rename
184
 
dirname = os.path.dirname
185
 
basename = os.path.basename
186
 
rmtree = shutil.rmtree
187
 
 
188
 
MIN_ABS_PATHLENGTH = 1
 
112
def normalizepath(f):
 
113
    if hasattr(os.path, 'realpath'):
 
114
        F = os.path.realpath
 
115
    else:
 
116
        F = os.path.abspath
 
117
    [p,e] = os.path.split(f)
 
118
    if e == "" or e == "." or e == "..":
 
119
        return F(f)
 
120
    else:
 
121
        return os.path.join(F(p), e)
189
122
 
190
123
if os.name == "posix":
191
124
    # In Python 2.4.2 and older, os.path.abspath and os.path.realpath
195
128
    _fs_enc = sys.getfilesystemencoding()
196
129
    def abspath(path):
197
130
        return os.path.abspath(path.encode(_fs_enc)).decode(_fs_enc)
198
 
 
199
131
    def realpath(path):
200
132
        return os.path.realpath(path.encode(_fs_enc)).decode(_fs_enc)
201
 
 
202
 
if sys.platform == 'win32':
 
133
else:
203
134
    # We need to use the Unicode-aware os.path.abspath and
204
135
    # 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)
225
 
 
226
 
    MIN_ABS_PATHLENGTH = 3
227
 
 
228
 
    def _win32_delete_readonly(function, path, excinfo):
229
 
        """Error handler for shutil.rmtree function [for win32]
230
 
        Helps to remove files and dirs marked as read-only.
231
 
        """
232
 
        type_, value = excinfo[:2]
233
 
        if function in (os.remove, os.rmdir) \
234
 
            and type_ == OSError \
235
 
            and value.errno == errno.EACCES:
236
 
            bzrlib.osutils.make_writable(path)
237
 
            function(path)
238
 
        else:
239
 
            raise
240
 
 
241
 
    def rmtree(path, ignore_errors=False, onerror=_win32_delete_readonly):
242
 
        """Replacer for shutil.rmtree: could remove readonly dirs/files"""
243
 
        return shutil.rmtree(path, ignore_errors, onerror)
244
 
 
245
 
 
246
 
def normalizepath(f):
247
 
    if hasattr(os.path, 'realpath'):
248
 
        F = realpath
249
 
    else:
250
 
        F = abspath
251
 
    [p,e] = os.path.split(f)
252
 
    if e == "" or e == "." or e == "..":
253
 
        return F(f)
254
 
    else:
255
 
        return pathjoin(F(p), e)
256
 
 
 
136
    abspath = os.path.abspath
 
137
    realpath = os.path.realpath
257
138
 
258
139
def backup_file(fn):
259
140
    """Copy a file to a backup.
282
163
    finally:
283
164
        outf.close()
284
165
 
 
166
if os.name == 'nt':
 
167
    import shutil
 
168
    rename = shutil.move
 
169
else:
 
170
    rename = os.rename
 
171
 
285
172
 
286
173
def isdir(f):
287
174
    """True if f is an accessible directory."""
308
195
def is_inside(dir, fname):
309
196
    """True if fname is inside dir.
310
197
    
311
 
    The parameters should typically be passed to osutils.normpath first, so
 
198
    The parameters should typically be passed to os.path.normpath first, so
312
199
    that . and .. and repeated slashes are eliminated, and the separators
313
200
    are canonical for the platform.
314
201
    
315
202
    The empty string as a dir name is taken as top-of-tree and matches 
316
203
    everything.
317
204
    
318
 
    >>> is_inside('src', pathjoin('src', 'foo.c'))
 
205
    >>> is_inside('src', os.path.join('src', 'foo.c'))
319
206
    True
320
207
    >>> is_inside('src', 'srccontrol')
321
208
    False
322
 
    >>> is_inside('src', pathjoin('src', 'a', 'a', 'a', 'foo.c'))
 
209
    >>> is_inside('src', os.path.join('src', 'a', 'a', 'a', 'foo.c'))
323
210
    True
324
211
    >>> is_inside('foo.c', 'foo.c')
325
212
    True
336
223
    if dir == '':
337
224
        return True
338
225
 
339
 
    if dir[-1] != '/':
340
 
        dir += '/'
 
226
    if dir[-1] != os.sep:
 
227
        dir += os.sep
341
228
 
342
229
    return fname.startswith(dir)
343
230
 
353
240
 
354
241
def pumpfile(fromfile, tofile):
355
242
    """Copy contents of one file to another."""
356
 
    BUFSIZE = 32768
357
 
    while True:
358
 
        b = fromfile.read(BUFSIZE)
359
 
        if not b:
360
 
            break
361
 
        tofile.write(b)
362
 
 
363
 
 
364
 
def file_iterator(input_file, readsize=32768):
365
 
    while True:
366
 
        b = input_file.read(readsize)
367
 
        if len(b) == 0:
368
 
            break
369
 
        yield b
 
243
    tofile.write(fromfile.read())
370
244
 
371
245
 
372
246
def sha_file(f):
466
340
    """Return size of given open file."""
467
341
    return os.fstat(f.fileno())[ST_SIZE]
468
342
 
469
 
 
470
343
# Define rand_bytes based on platform.
471
344
try:
472
345
    # Python 2.4 and later have os.urandom,
489
362
                n -= 1
490
363
            return s
491
364
 
492
 
 
493
 
ALNUM = '0123456789abcdefghijklmnopqrstuvwxyz'
494
 
def rand_chars(num):
495
 
    """Return a random string of num alphanumeric characters
496
 
    
497
 
    The result only contains lowercase chars because it may be used on 
498
 
    case-insensitive filesystems.
499
 
    """
500
 
    s = ''
501
 
    for raw_byte in rand_bytes(num):
502
 
        s += ALNUM[ord(raw_byte) % 36]
503
 
    return s
504
 
 
505
 
 
506
365
## TODO: We could later have path objects that remember their list
507
366
## decomposition (might be too tricksy though.)
508
367
 
543
402
    for f in p:
544
403
        if (f == '..') or (f == None) or (f == ''):
545
404
            raise BzrError("sorry, %r not allowed in path" % f)
546
 
    return pathjoin(*p)
 
405
    return os.path.join(*p)
547
406
 
548
407
 
549
408
def appendpath(p1, p2):
550
409
    if p1 == '':
551
410
        return p2
552
411
    else:
553
 
        return pathjoin(p1, p2)
 
412
        return os.path.join(p1, p2)
554
413
    
555
414
 
556
415
def split_lines(s):
557
416
    """Split s into lines, but without removing the newline characters."""
558
 
    lines = s.split('\n')
559
 
    result = [line + '\n' for line in lines[:-1]]
560
 
    if lines[-1]:
561
 
        result.append(lines[-1])
562
 
    return result
 
417
    return StringIO(s).readlines()
563
418
 
564
419
 
565
420
def hardlinks_good():
578
433
            raise
579
434
        copyfile(src, dest)
580
435
 
581
 
def delete_any(full_path):
582
 
    """Delete a file or directory."""
583
 
    try:
584
 
        os.unlink(full_path)
585
 
    except OSError, e:
586
 
    # We may be renaming a dangling inventory id
587
 
        if e.errno not in (errno.EISDIR, errno.EACCES, errno.EPERM):
588
 
            raise
589
 
        os.rmdir(full_path)
590
 
 
591
436
 
592
437
def has_symlinks():
593
438
    if hasattr(os, 'symlink'):
622
467
 
623
468
    os.path.commonprefix (python2.4) has a bad bug that it works just
624
469
    on string prefixes, assuming that '/u' is a prefix of '/u2'.  This
625
 
    avoids that problem.
626
 
    """
627
 
 
628
 
    assert len(base) >= MIN_ABS_PATHLENGTH, ('Length of base must be equal or'
629
 
        ' exceed the platform minimum length (which is %d)' % 
630
 
        MIN_ABS_PATHLENGTH)
 
470
    avoids that problem."""
631
471
    rp = abspath(path)
632
472
 
633
473
    s = []
641
481
    else:
642
482
        # XXX This should raise a NotChildPath exception, as its not tied
643
483
        # to branch anymore.
644
 
        raise PathNotChild(rp, base)
645
 
 
646
 
    if s:
647
 
        return pathjoin(*s)
648
 
    else:
649
 
        return ''
650
 
 
651
 
 
652
 
def safe_unicode(unicode_or_utf8_string):
653
 
    """Coerce unicode_or_utf8_string into unicode.
654
 
 
655
 
    If it is unicode, it is returned.
656
 
    Otherwise it is decoded from utf-8. If a decoding error
657
 
    occurs, it is wrapped as a If the decoding fails, the exception is wrapped 
658
 
    as a BzrBadParameter exception.
659
 
    """
660
 
    if isinstance(unicode_or_utf8_string, unicode):
661
 
        return unicode_or_utf8_string
662
 
    try:
663
 
        return unicode_or_utf8_string.decode('utf8')
664
 
    except UnicodeDecodeError:
665
 
        raise BzrBadParameterNotUnicode(unicode_or_utf8_string)
666
 
 
667
 
 
668
 
def terminal_width():
669
 
    """Return estimated terminal width."""
670
 
 
671
 
    # TODO: Do something smart on Windows?
672
 
 
673
 
    # TODO: Is there anything that gets a better update when the window
674
 
    # is resized while the program is running? We could use the Python termcap
675
 
    # library.
676
 
    try:
677
 
        return int(os.environ['COLUMNS'])
678
 
    except (IndexError, KeyError, ValueError):
679
 
        return 80
680
 
 
681
 
def supports_executable():
682
 
    return sys.platform != "win32"
683
 
 
684
 
 
685
 
def strip_trailing_slash(path):
686
 
    """Strip trailing slash, except for root paths.
687
 
    The definition of 'root path' is platform-dependent.
688
 
    """
689
 
    if len(path) != MIN_ABS_PATHLENGTH and path[-1] == '/':
690
 
        return path[:-1]
691
 
    else:
692
 
        return path
693
 
 
694
 
 
695
 
_validWin32PathRE = re.compile(r'^([A-Za-z]:[/\\])?[^:<>*"?\|]*$')
696
 
 
697
 
 
698
 
def check_legal_path(path):
699
 
    """Check whether the supplied path is legal.  
700
 
    This is only required on Windows, so we don't test on other platforms
701
 
    right now.
702
 
    """
703
 
    if sys.platform != "win32":
704
 
        return
705
 
    if _validWin32PathRE.match(path) is None:
706
 
        raise IllegalPath(path)
 
484
        raise NotBranchError("path %r is not within branch %r" % (rp, base))
 
485
 
 
486
    return os.sep.join(s)