/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/osutils.py

  • Committer: John Arbash Meinel
  • Date: 2006-07-03 18:27:35 UTC
  • mto: This revision was merged to the branch mainline in revision 1851.
  • Revision ID: john@arbash-meinel.com-20060703182735-3081f13e92d7f657
WorkingTree.open_containing() was directly calling os.getcwdu(), which on mac returns the wrong normalization, and on win32 would have the wrong slashes

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Bazaar-NG -- distributed version control
 
2
#
 
3
# Copyright (C) 2005 by Canonical Ltd
 
4
#
 
5
# This program is free software; you can redistribute it and/or modify
 
6
# it under the terms of the GNU General Public License as published by
 
7
# the Free Software Foundation; either version 2 of the License, or
 
8
# (at your option) any later version.
 
9
#
 
10
# This program is distributed in the hope that it will be useful,
 
11
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
13
# GNU General Public License for more details.
 
14
#
 
15
# You should have received a copy of the GNU General Public License
 
16
# along with this program; if not, write to the Free Software
 
17
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
18
 
 
19
from cStringIO import StringIO
 
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
                    splitdrive as _nt_splitdrive,
 
26
                    )
 
27
import os
 
28
from os import listdir
 
29
import posixpath
 
30
import re
 
31
import sha
 
32
import shutil
 
33
from shutil import copyfile
 
34
import stat
 
35
from stat import (S_ISREG, S_ISDIR, S_ISLNK, ST_MODE, ST_SIZE,
 
36
                  S_ISCHR, S_ISBLK, S_ISFIFO, S_ISSOCK)
 
37
import string
 
38
import sys
 
39
import time
 
40
import types
 
41
import tempfile
 
42
import unicodedata
 
43
 
 
44
import bzrlib
 
45
from bzrlib.errors import (BzrError,
 
46
                           BzrBadParameterNotUnicode,
 
47
                           NoSuchFile,
 
48
                           PathNotChild,
 
49
                           IllegalPath,
 
50
                           )
 
51
from bzrlib.symbol_versioning import (deprecated_function, 
 
52
        zero_nine)
 
53
from bzrlib.trace import mutter
 
54
 
 
55
 
 
56
def make_readonly(filename):
 
57
    """Make a filename read-only."""
 
58
    mod = os.stat(filename).st_mode
 
59
    mod = mod & 0777555
 
60
    os.chmod(filename, mod)
 
61
 
 
62
 
 
63
def make_writable(filename):
 
64
    mod = os.stat(filename).st_mode
 
65
    mod = mod | 0200
 
66
    os.chmod(filename, mod)
 
67
 
 
68
 
 
69
_QUOTE_RE = None
 
70
 
 
71
 
 
72
def quotefn(f):
 
73
    """Return a quoted filename filename
 
74
 
 
75
    This previously used backslash quoting, but that works poorly on
 
76
    Windows."""
 
77
    # TODO: I'm not really sure this is the best format either.x
 
78
    global _QUOTE_RE
 
79
    if _QUOTE_RE == None:
 
80
        _QUOTE_RE = re.compile(r'([^a-zA-Z0-9.,:/\\_~-])')
 
81
        
 
82
    if _QUOTE_RE.search(f):
 
83
        return '"' + f + '"'
 
84
    else:
 
85
        return f
 
86
 
 
87
 
 
88
_directory_kind = 'directory'
 
89
 
 
90
_formats = {
 
91
    stat.S_IFDIR:_directory_kind,
 
92
    stat.S_IFCHR:'chardev',
 
93
    stat.S_IFBLK:'block',
 
94
    stat.S_IFREG:'file',
 
95
    stat.S_IFIFO:'fifo',
 
96
    stat.S_IFLNK:'symlink',
 
97
    stat.S_IFSOCK:'socket',
 
98
}
 
99
 
 
100
 
 
101
def file_kind_from_stat_mode(stat_mode, _formats=_formats, _unknown='unknown'):
 
102
    """Generate a file kind from a stat mode. This is used in walkdirs.
 
103
 
 
104
    Its performance is critical: Do not mutate without careful benchmarking.
 
105
    """
 
106
    try:
 
107
        return _formats[stat_mode & 0170000]
 
108
    except KeyError:
 
109
        return _unknown
 
110
 
 
111
 
 
112
def file_kind(f, _lstat=os.lstat, _mapper=file_kind_from_stat_mode):
 
113
    try:
 
114
        return _mapper(_lstat(f).st_mode)
 
115
    except OSError, e:
 
116
        if getattr(e, 'errno', None) == errno.ENOENT:
 
117
            raise bzrlib.errors.NoSuchFile(f)
 
118
        raise
 
119
 
 
120
 
 
121
def kind_marker(kind):
 
122
    if kind == 'file':
 
123
        return ''
 
124
    elif kind == _directory_kind:
 
125
        return '/'
 
126
    elif kind == 'symlink':
 
127
        return '@'
 
128
    else:
 
129
        raise BzrError('invalid file kind %r' % kind)
 
130
 
 
131
lexists = getattr(os.path, 'lexists', None)
 
132
if lexists is None:
 
133
    def lexists(f):
 
134
        try:
 
135
            if hasattr(os, 'lstat'):
 
136
                os.lstat(f)
 
137
            else:
 
138
                os.stat(f)
 
139
            return True
 
140
        except OSError,e:
 
141
            if e.errno == errno.ENOENT:
 
142
                return False;
 
143
            else:
 
144
                raise BzrError("lstat/stat of (%r): %r" % (f, e))
 
145
 
 
146
 
 
147
def fancy_rename(old, new, rename_func, unlink_func):
 
148
    """A fancy rename, when you don't have atomic rename.
 
149
    
 
150
    :param old: The old path, to rename from
 
151
    :param new: The new path, to rename to
 
152
    :param rename_func: The potentially non-atomic rename function
 
153
    :param unlink_func: A way to delete the target file if the full rename succeeds
 
154
    """
 
155
 
 
156
    # sftp rename doesn't allow overwriting, so play tricks:
 
157
    import random
 
158
    base = os.path.basename(new)
 
159
    dirname = os.path.dirname(new)
 
160
    tmp_name = u'tmp.%s.%.9f.%d.%s' % (base, time.time(), os.getpid(), rand_chars(10))
 
161
    tmp_name = pathjoin(dirname, tmp_name)
 
162
 
 
163
    # Rename the file out of the way, but keep track if it didn't exist
 
164
    # We don't want to grab just any exception
 
165
    # something like EACCES should prevent us from continuing
 
166
    # The downside is that the rename_func has to throw an exception
 
167
    # with an errno = ENOENT, or NoSuchFile
 
168
    file_existed = False
 
169
    try:
 
170
        rename_func(new, tmp_name)
 
171
    except (NoSuchFile,), e:
 
172
        pass
 
173
    except IOError, e:
 
174
        # RBC 20060103 abstraction leakage: the paramiko SFTP clients rename
 
175
        # function raises an IOError with errno == None when a rename fails.
 
176
        # This then gets caught here.
 
177
        if e.errno not in (None, errno.ENOENT, errno.ENOTDIR):
 
178
            raise
 
179
    except Exception, e:
 
180
        if (not hasattr(e, 'errno') 
 
181
            or e.errno not in (errno.ENOENT, errno.ENOTDIR)):
 
182
            raise
 
183
    else:
 
184
        file_existed = True
 
185
 
 
186
    success = False
 
187
    try:
 
188
        # This may throw an exception, in which case success will
 
189
        # not be set.
 
190
        rename_func(old, new)
 
191
        success = True
 
192
    finally:
 
193
        if file_existed:
 
194
            # If the file used to exist, rename it back into place
 
195
            # otherwise just delete it from the tmp location
 
196
            if success:
 
197
                unlink_func(tmp_name)
 
198
            else:
 
199
                rename_func(tmp_name, new)
 
200
 
 
201
 
 
202
# In Python 2.4.2 and older, os.path.abspath and os.path.realpath
 
203
# choke on a Unicode string containing a relative path if
 
204
# os.getcwd() returns a non-sys.getdefaultencoding()-encoded
 
205
# string.
 
206
_fs_enc = sys.getfilesystemencoding()
 
207
def _posix_abspath(path):
 
208
    # jam 20060426 rather than encoding to fsencoding
 
209
    # copy posixpath.abspath, but use os.getcwdu instead
 
210
    if not posixpath.isabs(path):
 
211
        path = posixpath.join(getcwd(), path)
 
212
    return posixpath.normpath(path)
 
213
 
 
214
 
 
215
def _posix_realpath(path):
 
216
    return posixpath.realpath(path.encode(_fs_enc)).decode(_fs_enc)
 
217
 
 
218
 
 
219
def _win32_fixdrive(path):
 
220
    """Force drive letters to be consistent.
 
221
 
 
222
    win32 is inconsistent whether it returns lower or upper case
 
223
    and even if it was consistent the user might type the other
 
224
    so we force it to uppercase
 
225
    running python.exe under cmd.exe return capital C:\\
 
226
    running win32 python inside a cygwin shell returns lowercase c:\\
 
227
    """
 
228
    drive, path = _nt_splitdrive(path)
 
229
    return drive.upper() + path
 
230
 
 
231
 
 
232
def _win32_abspath(path):
 
233
    # Real _nt_abspath doesn't have a problem with a unicode cwd
 
234
    return _win32_fixdrive(_nt_abspath(unicode(path)).replace('\\', '/'))
 
235
 
 
236
 
 
237
def _win32_realpath(path):
 
238
    # Real _nt_realpath doesn't have a problem with a unicode cwd
 
239
    return _win32_fixdrive(_nt_realpath(unicode(path)).replace('\\', '/'))
 
240
 
 
241
 
 
242
def _win32_pathjoin(*args):
 
243
    return _nt_join(*args).replace('\\', '/')
 
244
 
 
245
 
 
246
def _win32_normpath(path):
 
247
    return _win32_fixdrive(_nt_normpath(unicode(path)).replace('\\', '/'))
 
248
 
 
249
 
 
250
def _win32_getcwd():
 
251
    return _win32_fixdrive(os.getcwdu().replace('\\', '/'))
 
252
 
 
253
 
 
254
def _win32_mkdtemp(*args, **kwargs):
 
255
    return _win32_fixdrive(tempfile.mkdtemp(*args, **kwargs).replace('\\', '/'))
 
256
 
 
257
 
 
258
def _win32_rename(old, new):
 
259
    """We expect to be able to atomically replace 'new' with old.
 
260
 
 
261
    On win32, if new exists, it must be moved out of the way first,
 
262
    and then deleted. 
 
263
    """
 
264
    try:
 
265
        fancy_rename(old, new, rename_func=os.rename, unlink_func=os.unlink)
 
266
    except OSError, e:
 
267
        if e.errno in (errno.EPERM, errno.EACCES, errno.EBUSY):
 
268
            # If we try to rename a non-existant file onto cwd, we get EPERM
 
269
            # instead of ENOENT, this will raise ENOENT if the old path
 
270
            # doesn't exist
 
271
            os.lstat(old)
 
272
        raise
 
273
 
 
274
 
 
275
def _mac_getcwd():
 
276
    return unicodedata.normalize('NFKC', os.getcwdu())
 
277
 
 
278
 
 
279
# Default is to just use the python builtins, but these can be rebound on
 
280
# particular platforms.
 
281
abspath = _posix_abspath
 
282
realpath = _posix_realpath
 
283
pathjoin = os.path.join
 
284
normpath = os.path.normpath
 
285
getcwd = os.getcwdu
 
286
mkdtemp = tempfile.mkdtemp
 
287
rename = os.rename
 
288
dirname = os.path.dirname
 
289
basename = os.path.basename
 
290
rmtree = shutil.rmtree
 
291
 
 
292
MIN_ABS_PATHLENGTH = 1
 
293
 
 
294
 
 
295
if sys.platform == 'win32':
 
296
    abspath = _win32_abspath
 
297
    realpath = _win32_realpath
 
298
    pathjoin = _win32_pathjoin
 
299
    normpath = _win32_normpath
 
300
    getcwd = _win32_getcwd
 
301
    mkdtemp = _win32_mkdtemp
 
302
    rename = _win32_rename
 
303
 
 
304
    MIN_ABS_PATHLENGTH = 3
 
305
 
 
306
    def _win32_delete_readonly(function, path, excinfo):
 
307
        """Error handler for shutil.rmtree function [for win32]
 
308
        Helps to remove files and dirs marked as read-only.
 
309
        """
 
310
        type_, value = excinfo[:2]
 
311
        if function in (os.remove, os.rmdir) \
 
312
            and type_ == OSError \
 
313
            and value.errno == errno.EACCES:
 
314
            bzrlib.osutils.make_writable(path)
 
315
            function(path)
 
316
        else:
 
317
            raise
 
318
 
 
319
    def rmtree(path, ignore_errors=False, onerror=_win32_delete_readonly):
 
320
        """Replacer for shutil.rmtree: could remove readonly dirs/files"""
 
321
        return shutil.rmtree(path, ignore_errors, onerror)
 
322
elif sys.platform == 'darwin':
 
323
    getcwd = _mac_getcwd
 
324
 
 
325
 
 
326
def get_terminal_encoding():
 
327
    """Find the best encoding for printing to the screen.
 
328
 
 
329
    This attempts to check both sys.stdout and sys.stdin to see
 
330
    what encoding they are in, and if that fails it falls back to
 
331
    bzrlib.user_encoding.
 
332
    The problem is that on Windows, locale.getpreferredencoding()
 
333
    is not the same encoding as that used by the console:
 
334
    http://mail.python.org/pipermail/python-list/2003-May/162357.html
 
335
 
 
336
    On my standard US Windows XP, the preferred encoding is
 
337
    cp1252, but the console is cp437
 
338
    """
 
339
    output_encoding = getattr(sys.stdout, 'encoding', None)
 
340
    if not output_encoding:
 
341
        input_encoding = getattr(sys.stdin, 'encoding', None)
 
342
        if not input_encoding:
 
343
            output_encoding = bzrlib.user_encoding
 
344
            mutter('encoding stdout as bzrlib.user_encoding %r', output_encoding)
 
345
        else:
 
346
            output_encoding = input_encoding
 
347
            mutter('encoding stdout as sys.stdin encoding %r', output_encoding)
 
348
    else:
 
349
        mutter('encoding stdout as sys.stdout encoding %r', output_encoding)
 
350
    return output_encoding
 
351
 
 
352
 
 
353
def normalizepath(f):
 
354
    if hasattr(os.path, 'realpath'):
 
355
        F = realpath
 
356
    else:
 
357
        F = abspath
 
358
    [p,e] = os.path.split(f)
 
359
    if e == "" or e == "." or e == "..":
 
360
        return F(f)
 
361
    else:
 
362
        return pathjoin(F(p), e)
 
363
 
 
364
 
 
365
def backup_file(fn):
 
366
    """Copy a file to a backup.
 
367
 
 
368
    Backups are named in GNU-style, with a ~ suffix.
 
369
 
 
370
    If the file is already a backup, it's not copied.
 
371
    """
 
372
    if fn[-1] == '~':
 
373
        return
 
374
    bfn = fn + '~'
 
375
 
 
376
    if has_symlinks() and os.path.islink(fn):
 
377
        target = os.readlink(fn)
 
378
        os.symlink(target, bfn)
 
379
        return
 
380
    inf = file(fn, 'rb')
 
381
    try:
 
382
        content = inf.read()
 
383
    finally:
 
384
        inf.close()
 
385
    
 
386
    outf = file(bfn, 'wb')
 
387
    try:
 
388
        outf.write(content)
 
389
    finally:
 
390
        outf.close()
 
391
 
 
392
 
 
393
def isdir(f):
 
394
    """True if f is an accessible directory."""
 
395
    try:
 
396
        return S_ISDIR(os.lstat(f)[ST_MODE])
 
397
    except OSError:
 
398
        return False
 
399
 
 
400
 
 
401
def isfile(f):
 
402
    """True if f is a regular file."""
 
403
    try:
 
404
        return S_ISREG(os.lstat(f)[ST_MODE])
 
405
    except OSError:
 
406
        return False
 
407
 
 
408
def islink(f):
 
409
    """True if f is a symlink."""
 
410
    try:
 
411
        return S_ISLNK(os.lstat(f)[ST_MODE])
 
412
    except OSError:
 
413
        return False
 
414
 
 
415
def is_inside(dir, fname):
 
416
    """True if fname is inside dir.
 
417
    
 
418
    The parameters should typically be passed to osutils.normpath first, so
 
419
    that . and .. and repeated slashes are eliminated, and the separators
 
420
    are canonical for the platform.
 
421
    
 
422
    The empty string as a dir name is taken as top-of-tree and matches 
 
423
    everything.
 
424
    
 
425
    >>> is_inside('src', pathjoin('src', 'foo.c'))
 
426
    True
 
427
    >>> is_inside('src', 'srccontrol')
 
428
    False
 
429
    >>> is_inside('src', pathjoin('src', 'a', 'a', 'a', 'foo.c'))
 
430
    True
 
431
    >>> is_inside('foo.c', 'foo.c')
 
432
    True
 
433
    >>> is_inside('foo.c', '')
 
434
    False
 
435
    >>> is_inside('', 'foo.c')
 
436
    True
 
437
    """
 
438
    # XXX: Most callers of this can actually do something smarter by 
 
439
    # looking at the inventory
 
440
    if dir == fname:
 
441
        return True
 
442
    
 
443
    if dir == '':
 
444
        return True
 
445
 
 
446
    if dir[-1] != '/':
 
447
        dir += '/'
 
448
 
 
449
    return fname.startswith(dir)
 
450
 
 
451
 
 
452
def is_inside_any(dir_list, fname):
 
453
    """True if fname is inside any of given dirs."""
 
454
    for dirname in dir_list:
 
455
        if is_inside(dirname, fname):
 
456
            return True
 
457
    else:
 
458
        return False
 
459
 
 
460
 
 
461
def is_inside_or_parent_of_any(dir_list, fname):
 
462
    """True if fname is a child or a parent of any of the given files."""
 
463
    for dirname in dir_list:
 
464
        if is_inside(dirname, fname) or is_inside(fname, dirname):
 
465
            return True
 
466
    else:
 
467
        return False
 
468
 
 
469
 
 
470
def pumpfile(fromfile, tofile):
 
471
    """Copy contents of one file to another."""
 
472
    BUFSIZE = 32768
 
473
    while True:
 
474
        b = fromfile.read(BUFSIZE)
 
475
        if not b:
 
476
            break
 
477
        tofile.write(b)
 
478
 
 
479
 
 
480
def file_iterator(input_file, readsize=32768):
 
481
    while True:
 
482
        b = input_file.read(readsize)
 
483
        if len(b) == 0:
 
484
            break
 
485
        yield b
 
486
 
 
487
 
 
488
def sha_file(f):
 
489
    if hasattr(f, 'tell'):
 
490
        assert f.tell() == 0
 
491
    s = sha.new()
 
492
    BUFSIZE = 128<<10
 
493
    while True:
 
494
        b = f.read(BUFSIZE)
 
495
        if not b:
 
496
            break
 
497
        s.update(b)
 
498
    return s.hexdigest()
 
499
 
 
500
 
 
501
 
 
502
def sha_strings(strings):
 
503
    """Return the sha-1 of concatenation of strings"""
 
504
    s = sha.new()
 
505
    map(s.update, strings)
 
506
    return s.hexdigest()
 
507
 
 
508
 
 
509
def sha_string(f):
 
510
    s = sha.new()
 
511
    s.update(f)
 
512
    return s.hexdigest()
 
513
 
 
514
 
 
515
def fingerprint_file(f):
 
516
    s = sha.new()
 
517
    b = f.read()
 
518
    s.update(b)
 
519
    size = len(b)
 
520
    return {'size': size,
 
521
            'sha1': s.hexdigest()}
 
522
 
 
523
 
 
524
def compare_files(a, b):
 
525
    """Returns true if equal in contents"""
 
526
    BUFSIZE = 4096
 
527
    while True:
 
528
        ai = a.read(BUFSIZE)
 
529
        bi = b.read(BUFSIZE)
 
530
        if ai != bi:
 
531
            return False
 
532
        if ai == '':
 
533
            return True
 
534
 
 
535
 
 
536
def local_time_offset(t=None):
 
537
    """Return offset of local zone from GMT, either at present or at time t."""
 
538
    # python2.3 localtime() can't take None
 
539
    if t == None:
 
540
        t = time.time()
 
541
        
 
542
    if time.localtime(t).tm_isdst and time.daylight:
 
543
        return -time.altzone
 
544
    else:
 
545
        return -time.timezone
 
546
 
 
547
    
 
548
def format_date(t, offset=0, timezone='original', date_fmt=None, 
 
549
                show_offset=True):
 
550
    ## TODO: Perhaps a global option to use either universal or local time?
 
551
    ## Or perhaps just let people set $TZ?
 
552
    assert isinstance(t, float)
 
553
    
 
554
    if timezone == 'utc':
 
555
        tt = time.gmtime(t)
 
556
        offset = 0
 
557
    elif timezone == 'original':
 
558
        if offset == None:
 
559
            offset = 0
 
560
        tt = time.gmtime(t + offset)
 
561
    elif timezone == 'local':
 
562
        tt = time.localtime(t)
 
563
        offset = local_time_offset(t)
 
564
    else:
 
565
        raise BzrError("unsupported timezone format %r" % timezone,
 
566
                       ['options are "utc", "original", "local"'])
 
567
    if date_fmt is None:
 
568
        date_fmt = "%a %Y-%m-%d %H:%M:%S"
 
569
    if show_offset:
 
570
        offset_str = ' %+03d%02d' % (offset / 3600, (offset / 60) % 60)
 
571
    else:
 
572
        offset_str = ''
 
573
    return (time.strftime(date_fmt, tt) +  offset_str)
 
574
 
 
575
 
 
576
def compact_date(when):
 
577
    return time.strftime('%Y%m%d%H%M%S', time.gmtime(when))
 
578
    
 
579
 
 
580
 
 
581
def filesize(f):
 
582
    """Return size of given open file."""
 
583
    return os.fstat(f.fileno())[ST_SIZE]
 
584
 
 
585
 
 
586
# Define rand_bytes based on platform.
 
587
try:
 
588
    # Python 2.4 and later have os.urandom,
 
589
    # but it doesn't work on some arches
 
590
    os.urandom(1)
 
591
    rand_bytes = os.urandom
 
592
except (NotImplementedError, AttributeError):
 
593
    # If python doesn't have os.urandom, or it doesn't work,
 
594
    # then try to first pull random data from /dev/urandom
 
595
    if os.path.exists("/dev/urandom"):
 
596
        rand_bytes = file('/dev/urandom', 'rb').read
 
597
    # Otherwise, use this hack as a last resort
 
598
    else:
 
599
        # not well seeded, but better than nothing
 
600
        def rand_bytes(n):
 
601
            import random
 
602
            s = ''
 
603
            while n:
 
604
                s += chr(random.randint(0, 255))
 
605
                n -= 1
 
606
            return s
 
607
 
 
608
 
 
609
ALNUM = '0123456789abcdefghijklmnopqrstuvwxyz'
 
610
def rand_chars(num):
 
611
    """Return a random string of num alphanumeric characters
 
612
    
 
613
    The result only contains lowercase chars because it may be used on 
 
614
    case-insensitive filesystems.
 
615
    """
 
616
    s = ''
 
617
    for raw_byte in rand_bytes(num):
 
618
        s += ALNUM[ord(raw_byte) % 36]
 
619
    return s
 
620
 
 
621
 
 
622
## TODO: We could later have path objects that remember their list
 
623
## decomposition (might be too tricksy though.)
 
624
 
 
625
def splitpath(p):
 
626
    """Turn string into list of parts.
 
627
 
 
628
    >>> splitpath('a')
 
629
    ['a']
 
630
    >>> splitpath('a/b')
 
631
    ['a', 'b']
 
632
    >>> splitpath('a/./b')
 
633
    ['a', 'b']
 
634
    >>> splitpath('a/.b')
 
635
    ['a', '.b']
 
636
    >>> splitpath('a/../b')
 
637
    Traceback (most recent call last):
 
638
    ...
 
639
    BzrError: sorry, '..' not allowed in path
 
640
    """
 
641
    assert isinstance(p, types.StringTypes)
 
642
 
 
643
    # split on either delimiter because people might use either on
 
644
    # Windows
 
645
    ps = re.split(r'[\\/]', p)
 
646
 
 
647
    rps = []
 
648
    for f in ps:
 
649
        if f == '..':
 
650
            raise BzrError("sorry, %r not allowed in path" % f)
 
651
        elif (f == '.') or (f == ''):
 
652
            pass
 
653
        else:
 
654
            rps.append(f)
 
655
    return rps
 
656
 
 
657
def joinpath(p):
 
658
    assert isinstance(p, list)
 
659
    for f in p:
 
660
        if (f == '..') or (f == None) or (f == ''):
 
661
            raise BzrError("sorry, %r not allowed in path" % f)
 
662
    return pathjoin(*p)
 
663
 
 
664
 
 
665
@deprecated_function(zero_nine)
 
666
def appendpath(p1, p2):
 
667
    if p1 == '':
 
668
        return p2
 
669
    else:
 
670
        return pathjoin(p1, p2)
 
671
    
 
672
 
 
673
def split_lines(s):
 
674
    """Split s into lines, but without removing the newline characters."""
 
675
    lines = s.split('\n')
 
676
    result = [line + '\n' for line in lines[:-1]]
 
677
    if lines[-1]:
 
678
        result.append(lines[-1])
 
679
    return result
 
680
 
 
681
 
 
682
def hardlinks_good():
 
683
    return sys.platform not in ('win32', 'cygwin', 'darwin')
 
684
 
 
685
 
 
686
def link_or_copy(src, dest):
 
687
    """Hardlink a file, or copy it if it can't be hardlinked."""
 
688
    if not hardlinks_good():
 
689
        copyfile(src, dest)
 
690
        return
 
691
    try:
 
692
        os.link(src, dest)
 
693
    except (OSError, IOError), e:
 
694
        if e.errno != errno.EXDEV:
 
695
            raise
 
696
        copyfile(src, dest)
 
697
 
 
698
def delete_any(full_path):
 
699
    """Delete a file or directory."""
 
700
    try:
 
701
        os.unlink(full_path)
 
702
    except OSError, e:
 
703
    # We may be renaming a dangling inventory id
 
704
        if e.errno not in (errno.EISDIR, errno.EACCES, errno.EPERM):
 
705
            raise
 
706
        os.rmdir(full_path)
 
707
 
 
708
 
 
709
def has_symlinks():
 
710
    if hasattr(os, 'symlink'):
 
711
        return True
 
712
    else:
 
713
        return False
 
714
        
 
715
 
 
716
def contains_whitespace(s):
 
717
    """True if there are any whitespace characters in s."""
 
718
    for ch in string.whitespace:
 
719
        if ch in s:
 
720
            return True
 
721
    else:
 
722
        return False
 
723
 
 
724
 
 
725
def contains_linebreaks(s):
 
726
    """True if there is any vertical whitespace in s."""
 
727
    for ch in '\f\n\r':
 
728
        if ch in s:
 
729
            return True
 
730
    else:
 
731
        return False
 
732
 
 
733
 
 
734
def relpath(base, path):
 
735
    """Return path relative to base, or raise exception.
 
736
 
 
737
    The path may be either an absolute path or a path relative to the
 
738
    current working directory.
 
739
 
 
740
    os.path.commonprefix (python2.4) has a bad bug that it works just
 
741
    on string prefixes, assuming that '/u' is a prefix of '/u2'.  This
 
742
    avoids that problem.
 
743
    """
 
744
 
 
745
    assert len(base) >= MIN_ABS_PATHLENGTH, ('Length of base must be equal or'
 
746
        ' exceed the platform minimum length (which is %d)' % 
 
747
        MIN_ABS_PATHLENGTH)
 
748
 
 
749
    rp = abspath(path)
 
750
 
 
751
    s = []
 
752
    head = rp
 
753
    while len(head) >= len(base):
 
754
        if head == base:
 
755
            break
 
756
        head, tail = os.path.split(head)
 
757
        if tail:
 
758
            s.insert(0, tail)
 
759
    else:
 
760
        raise PathNotChild(rp, base)
 
761
 
 
762
    if s:
 
763
        return pathjoin(*s)
 
764
    else:
 
765
        return ''
 
766
 
 
767
 
 
768
def safe_unicode(unicode_or_utf8_string):
 
769
    """Coerce unicode_or_utf8_string into unicode.
 
770
 
 
771
    If it is unicode, it is returned.
 
772
    Otherwise it is decoded from utf-8. If a decoding error
 
773
    occurs, it is wrapped as a If the decoding fails, the exception is wrapped 
 
774
    as a BzrBadParameter exception.
 
775
    """
 
776
    if isinstance(unicode_or_utf8_string, unicode):
 
777
        return unicode_or_utf8_string
 
778
    try:
 
779
        return unicode_or_utf8_string.decode('utf8')
 
780
    except UnicodeDecodeError:
 
781
        raise BzrBadParameterNotUnicode(unicode_or_utf8_string)
 
782
 
 
783
 
 
784
_platform_normalizes_filenames = False
 
785
if sys.platform == 'darwin':
 
786
    _platform_normalizes_filenames = True
 
787
 
 
788
 
 
789
def normalizes_filenames():
 
790
    """Return True if this platform normalizes unicode filenames.
 
791
 
 
792
    Mac OSX does, Windows/Linux do not.
 
793
    """
 
794
    return _platform_normalizes_filenames
 
795
 
 
796
 
 
797
def _accessible_normalized_filename(path):
 
798
    """Get the unicode normalized path, and if you can access the file.
 
799
 
 
800
    On platforms where the system normalizes filenames (Mac OSX),
 
801
    you can access a file by any path which will normalize correctly.
 
802
    On platforms where the system does not normalize filenames 
 
803
    (Windows, Linux), you have to access a file by its exact path.
 
804
 
 
805
    Internally, bzr only supports NFC/NFKC normalization, since that is 
 
806
    the standard for XML documents.
 
807
 
 
808
    So return the normalized path, and a flag indicating if the file
 
809
    can be accessed by that path.
 
810
    """
 
811
 
 
812
    return unicodedata.normalize('NFKC', unicode(path)), True
 
813
 
 
814
 
 
815
def _inaccessible_normalized_filename(path):
 
816
    __doc__ = _accessible_normalized_filename.__doc__
 
817
 
 
818
    normalized = unicodedata.normalize('NFKC', unicode(path))
 
819
    return normalized, normalized == path
 
820
 
 
821
 
 
822
if _platform_normalizes_filenames:
 
823
    normalized_filename = _accessible_normalized_filename
 
824
else:
 
825
    normalized_filename = _inaccessible_normalized_filename
 
826
 
 
827
 
 
828
def terminal_width():
 
829
    """Return estimated terminal width."""
 
830
    if sys.platform == 'win32':
 
831
        import bzrlib.win32console
 
832
        return bzrlib.win32console.get_console_size()[0]
 
833
    width = 0
 
834
    try:
 
835
        import struct, fcntl, termios
 
836
        s = struct.pack('HHHH', 0, 0, 0, 0)
 
837
        x = fcntl.ioctl(1, termios.TIOCGWINSZ, s)
 
838
        width = struct.unpack('HHHH', x)[1]
 
839
    except IOError:
 
840
        pass
 
841
    if width <= 0:
 
842
        try:
 
843
            width = int(os.environ['COLUMNS'])
 
844
        except:
 
845
            pass
 
846
    if width <= 0:
 
847
        width = 80
 
848
 
 
849
    return width
 
850
 
 
851
def supports_executable():
 
852
    return sys.platform != "win32"
 
853
 
 
854
 
 
855
_validWin32PathRE = re.compile(r'^([A-Za-z]:[/\\])?[^:<>*"?\|]*$')
 
856
 
 
857
 
 
858
def check_legal_path(path):
 
859
    """Check whether the supplied path is legal.  
 
860
    This is only required on Windows, so we don't test on other platforms
 
861
    right now.
 
862
    """
 
863
    if sys.platform != "win32":
 
864
        return
 
865
    if _validWin32PathRE.match(path) is None:
 
866
        raise IllegalPath(path)
 
867
 
 
868
 
 
869
def walkdirs(top, prefix=""):
 
870
    """Yield data about all the directories in a tree.
 
871
    
 
872
    This yields all the data about the contents of a directory at a time.
 
873
    After each directory has been yielded, if the caller has mutated the list
 
874
    to exclude some directories, they are then not descended into.
 
875
    
 
876
    The data yielded is of the form:
 
877
    [(relpath, basename, kind, lstat, path_from_top), ...]
 
878
 
 
879
    :param prefix: Prefix the relpaths that are yielded with 'prefix'. This 
 
880
        allows one to walk a subtree but get paths that are relative to a tree
 
881
        rooted higher up.
 
882
    :return: an iterator over the dirs.
 
883
    """
 
884
    lstat = os.lstat
 
885
    pending = []
 
886
    _directory = _directory_kind
 
887
    _listdir = listdir
 
888
    pending = [(prefix, "", _directory, None, top)]
 
889
    while pending:
 
890
        dirblock = []
 
891
        currentdir = pending.pop()
 
892
        # 0 - relpath, 1- basename, 2- kind, 3- stat, 4-toppath
 
893
        top = currentdir[4]
 
894
        if currentdir[0]:
 
895
            relroot = currentdir[0] + '/'
 
896
        else:
 
897
            relroot = ""
 
898
        for name in sorted(_listdir(top)):
 
899
            abspath = top + '/' + name
 
900
            statvalue = lstat(abspath)
 
901
            dirblock.append ((relroot + name, name, file_kind_from_stat_mode(statvalue.st_mode), statvalue, abspath))
 
902
        yield dirblock
 
903
        # push the user specified dirs from dirblock
 
904
        for dir in reversed(dirblock):
 
905
            if dir[2] == _directory:
 
906
                pending.append(dir)
 
907
 
 
908
 
 
909
def path_prefix_key(path):
 
910
    """Generate a prefix-order path key for path.
 
911
 
 
912
    This can be used to sort paths in the same way that walkdirs does.
 
913
    """
 
914
    return (dirname(path) , path)
 
915
 
 
916
 
 
917
def compare_paths_prefix_order(path_a, path_b):
 
918
    """Compare path_a and path_b to generate the same order walkdirs uses."""
 
919
    key_a = path_prefix_key(path_a)
 
920
    key_b = path_prefix_key(path_b)
 
921
    return cmp(key_a, key_b)