~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/osutils.py

  • Committer: Martin Pool
  • Date: 2006-04-18 04:38:44 UTC
  • mto: This revision was merged to the branch mainline in revision 1670.
  • Revision ID: mbp@sourcefrog.net-20060418043844-dbaac5bdecaed3d1
Clear out BRANCH.TODO - most of them are done now

Show diffs side-by-side

added added

removed removed

Lines of Context:
28
28
import sys
29
29
import time
30
30
import types
 
31
import tempfile
31
32
 
32
33
import bzrlib
33
 
from bzrlib.errors import BzrError, NotBranchError
 
34
from bzrlib.errors import (BzrError,
 
35
                           BzrBadParameterNotUnicode,
 
36
                           NoSuchFile,
 
37
                           PathNotChild,
 
38
                           )
34
39
from bzrlib.trace import mutter
35
40
 
36
41
 
97
102
        raise BzrError('invalid file kind %r' % kind)
98
103
 
99
104
def lexists(f):
 
105
    if hasattr(os.path, 'lexists'):
 
106
        return os.path.lexists(f)
100
107
    try:
101
108
        if hasattr(os, 'lstat'):
102
109
            os.lstat(f)
109
116
        else:
110
117
            raise BzrError("lstat/stat of (%r): %r" % (f, e))
111
118
 
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)
 
119
def fancy_rename(old, new, rename_func, unlink_func):
 
120
    """A fancy rename, when you don't have atomic rename.
 
121
    
 
122
    :param old: The old path, to rename from
 
123
    :param new: The new path, to rename to
 
124
    :param rename_func: The potentially non-atomic rename function
 
125
    :param unlink_func: A way to delete the target file if the full rename succeeds
 
126
    """
 
127
 
 
128
    # sftp rename doesn't allow overwriting, so play tricks:
 
129
    import random
 
130
    base = os.path.basename(new)
 
131
    dirname = os.path.dirname(new)
 
132
    tmp_name = u'tmp.%s.%.9f.%d.%s' % (base, time.time(), os.getpid(), rand_chars(10))
 
133
    tmp_name = pathjoin(dirname, tmp_name)
 
134
 
 
135
    # Rename the file out of the way, but keep track if it didn't exist
 
136
    # We don't want to grab just any exception
 
137
    # something like EACCES should prevent us from continuing
 
138
    # The downside is that the rename_func has to throw an exception
 
139
    # with an errno = ENOENT, or NoSuchFile
 
140
    file_existed = False
 
141
    try:
 
142
        rename_func(new, tmp_name)
 
143
    except (NoSuchFile,), e:
 
144
        pass
 
145
    except IOError, e:
 
146
        # RBC 20060103 abstraction leakage: the paramiko SFTP clients rename
 
147
        # function raises an IOError with errno == None when a rename fails.
 
148
        # This then gets caught here.
 
149
        if e.errno not in (None, errno.ENOENT, errno.ENOTDIR):
 
150
            raise
 
151
    except Exception, e:
 
152
        if (not hasattr(e, 'errno') 
 
153
            or e.errno not in (errno.ENOENT, errno.ENOTDIR)):
 
154
            raise
 
155
    else:
 
156
        file_existed = True
 
157
 
 
158
    success = False
 
159
    try:
 
160
        # This may throw an exception, in which case success will
 
161
        # not be set.
 
162
        rename_func(old, new)
 
163
        success = True
 
164
    finally:
 
165
        if file_existed:
 
166
            # If the file used to exist, rename it back into place
 
167
            # otherwise just delete it from the tmp location
 
168
            if success:
 
169
                unlink_func(tmp_name)
 
170
            else:
 
171
                rename_func(tmp_name, new)
 
172
 
 
173
# Default is to just use the python builtins
 
174
abspath = os.path.abspath
 
175
realpath = os.path.realpath
 
176
pathjoin = os.path.join
 
177
normpath = os.path.normpath
 
178
getcwd = os.getcwdu
 
179
mkdtemp = tempfile.mkdtemp
 
180
rename = os.rename
 
181
dirname = os.path.dirname
 
182
basename = os.path.basename
122
183
 
123
184
if os.name == "posix":
124
185
    # In Python 2.4.2 and older, os.path.abspath and os.path.realpath
128
189
    _fs_enc = sys.getfilesystemencoding()
129
190
    def abspath(path):
130
191
        return os.path.abspath(path.encode(_fs_enc)).decode(_fs_enc)
 
192
 
131
193
    def realpath(path):
132
194
        return os.path.realpath(path.encode(_fs_enc)).decode(_fs_enc)
133
 
else:
 
195
 
 
196
if sys.platform == 'win32':
134
197
    # We need to use the Unicode-aware os.path.abspath and
135
198
    # os.path.realpath on Windows systems.
136
 
    abspath = os.path.abspath
137
 
    realpath = os.path.realpath
 
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)
 
219
 
 
220
 
 
221
def normalizepath(f):
 
222
    if hasattr(os.path, 'realpath'):
 
223
        F = realpath
 
224
    else:
 
225
        F = abspath
 
226
    [p,e] = os.path.split(f)
 
227
    if e == "" or e == "." or e == "..":
 
228
        return F(f)
 
229
    else:
 
230
        return pathjoin(F(p), e)
 
231
 
138
232
 
139
233
def backup_file(fn):
140
234
    """Copy a file to a backup.
163
257
    finally:
164
258
        outf.close()
165
259
 
166
 
if os.name == 'nt':
167
 
    import shutil
168
 
    rename = shutil.move
169
 
else:
170
 
    rename = os.rename
171
 
 
172
260
 
173
261
def isdir(f):
174
262
    """True if f is an accessible directory."""
195
283
def is_inside(dir, fname):
196
284
    """True if fname is inside dir.
197
285
    
198
 
    The parameters should typically be passed to os.path.normpath first, so
 
286
    The parameters should typically be passed to osutils.normpath first, so
199
287
    that . and .. and repeated slashes are eliminated, and the separators
200
288
    are canonical for the platform.
201
289
    
202
290
    The empty string as a dir name is taken as top-of-tree and matches 
203
291
    everything.
204
292
    
205
 
    >>> is_inside('src', os.path.join('src', 'foo.c'))
 
293
    >>> is_inside('src', pathjoin('src', 'foo.c'))
206
294
    True
207
295
    >>> is_inside('src', 'srccontrol')
208
296
    False
209
 
    >>> is_inside('src', os.path.join('src', 'a', 'a', 'a', 'foo.c'))
 
297
    >>> is_inside('src', pathjoin('src', 'a', 'a', 'a', 'foo.c'))
210
298
    True
211
299
    >>> is_inside('foo.c', 'foo.c')
212
300
    True
223
311
    if dir == '':
224
312
        return True
225
313
 
226
 
    if dir[-1] != os.sep:
227
 
        dir += os.sep
 
314
    if dir[-1] != '/':
 
315
        dir += '/'
228
316
 
229
317
    return fname.startswith(dir)
230
318
 
240
328
 
241
329
def pumpfile(fromfile, tofile):
242
330
    """Copy contents of one file to another."""
243
 
    tofile.write(fromfile.read())
 
331
    BUFSIZE = 32768
 
332
    while True:
 
333
        b = fromfile.read(BUFSIZE)
 
334
        if not b:
 
335
            break
 
336
        tofile.write(b)
 
337
 
 
338
 
 
339
def file_iterator(input_file, readsize=32768):
 
340
    while True:
 
341
        b = input_file.read(readsize)
 
342
        if len(b) == 0:
 
343
            break
 
344
        yield b
244
345
 
245
346
 
246
347
def sha_file(f):
340
441
    """Return size of given open file."""
341
442
    return os.fstat(f.fileno())[ST_SIZE]
342
443
 
 
444
 
343
445
# Define rand_bytes based on platform.
344
446
try:
345
447
    # Python 2.4 and later have os.urandom,
362
464
                n -= 1
363
465
            return s
364
466
 
 
467
 
 
468
ALNUM = '0123456789abcdefghijklmnopqrstuvwxyz'
 
469
def rand_chars(num):
 
470
    """Return a random string of num alphanumeric characters
 
471
    
 
472
    The result only contains lowercase chars because it may be used on 
 
473
    case-insensitive filesystems.
 
474
    """
 
475
    s = ''
 
476
    for raw_byte in rand_bytes(num):
 
477
        s += ALNUM[ord(raw_byte) % 36]
 
478
    return s
 
479
 
 
480
 
365
481
## TODO: We could later have path objects that remember their list
366
482
## decomposition (might be too tricksy though.)
367
483
 
402
518
    for f in p:
403
519
        if (f == '..') or (f == None) or (f == ''):
404
520
            raise BzrError("sorry, %r not allowed in path" % f)
405
 
    return os.path.join(*p)
 
521
    return pathjoin(*p)
406
522
 
407
523
 
408
524
def appendpath(p1, p2):
409
525
    if p1 == '':
410
526
        return p2
411
527
    else:
412
 
        return os.path.join(p1, p2)
 
528
        return pathjoin(p1, p2)
413
529
    
414
530
 
415
531
def split_lines(s):
433
549
            raise
434
550
        copyfile(src, dest)
435
551
 
 
552
def delete_any(full_path):
 
553
    """Delete a file or directory."""
 
554
    try:
 
555
        os.unlink(full_path)
 
556
    except OSError, e:
 
557
    # We may be renaming a dangling inventory id
 
558
        if e.errno not in (errno.EISDIR, errno.EACCES, errno.EPERM):
 
559
            raise
 
560
        os.rmdir(full_path)
 
561
 
436
562
 
437
563
def has_symlinks():
438
564
    if hasattr(os, 'symlink'):
467
593
 
468
594
    os.path.commonprefix (python2.4) has a bad bug that it works just
469
595
    on string prefixes, assuming that '/u' is a prefix of '/u2'.  This
470
 
    avoids that problem."""
 
596
    avoids that problem.
 
597
    """
 
598
    if sys.platform != "win32":
 
599
        minlength = 1
 
600
    else:
 
601
        minlength = 3
 
602
    assert len(base) >= minlength, ('Length of base must be equal or exceed the'
 
603
        ' platform minimum length (which is %d)' % minlength)
471
604
    rp = abspath(path)
472
605
 
473
606
    s = []
481
614
    else:
482
615
        # XXX This should raise a NotChildPath exception, as its not tied
483
616
        # to branch anymore.
484
 
        raise NotBranchError("path %r is not within branch %r" % (rp, base))
485
 
 
486
 
    return os.sep.join(s)
 
617
        raise PathNotChild(rp, base)
 
618
 
 
619
    if s:
 
620
        return pathjoin(*s)
 
621
    else:
 
622
        return ''
 
623
 
 
624
 
 
625
def safe_unicode(unicode_or_utf8_string):
 
626
    """Coerce unicode_or_utf8_string into unicode.
 
627
 
 
628
    If it is unicode, it is returned.
 
629
    Otherwise it is decoded from utf-8. If a decoding error
 
630
    occurs, it is wrapped as a If the decoding fails, the exception is wrapped 
 
631
    as a BzrBadParameter exception.
 
632
    """
 
633
    if isinstance(unicode_or_utf8_string, unicode):
 
634
        return unicode_or_utf8_string
 
635
    try:
 
636
        return unicode_or_utf8_string.decode('utf8')
 
637
    except UnicodeDecodeError:
 
638
        raise BzrBadParameterNotUnicode(unicode_or_utf8_string)
 
639
 
 
640
 
 
641
def terminal_width():
 
642
    """Return estimated terminal width."""
 
643
 
 
644
    # TODO: Do something smart on Windows?
 
645
 
 
646
    # TODO: Is there anything that gets a better update when the window
 
647
    # is resized while the program is running? We could use the Python termcap
 
648
    # library.
 
649
    try:
 
650
        return int(os.environ['COLUMNS'])
 
651
    except (IndexError, KeyError, ValueError):
 
652
        return 80
 
653
 
 
654
def supports_executable():
 
655
    return sys.platform != "win32"