~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/osutils.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2006-02-22 07:59:56 UTC
  • mfrom: (1553.5.33 bzr.mbp.locks)
  • Revision ID: pqm@pqm.ubuntu.com-20060222075956-fb281c427e571da6
add LockDir and related fixes

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.config import config_dir, _get_user_id
34
 
from bzrlib.errors import BzrError
 
34
from bzrlib.errors import (BzrError,
 
35
                           BzrBadParameterNotUnicode,
 
36
                           NoSuchFile,
 
37
                           PathNotChild,
 
38
                           )
35
39
from bzrlib.trace import mutter
36
40
 
37
41
 
98
102
        raise BzrError('invalid file kind %r' % kind)
99
103
 
100
104
def lexists(f):
 
105
    if hasattr(os.path, 'lexists'):
 
106
        return os.path.lexists(f)
101
107
    try:
102
108
        if hasattr(os, 'lstat'):
103
109
            os.lstat(f)
110
116
        else:
111
117
            raise BzrError("lstat/stat of (%r): %r" % (f, e))
112
118
 
 
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
 
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)
 
195
 
 
196
if sys.platform == 'win32':
 
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)
 
219
 
 
220
 
113
221
def normalizepath(f):
114
222
    if hasattr(os.path, 'realpath'):
115
 
        F = os.path.realpath
 
223
        F = realpath
116
224
    else:
117
 
        F = os.path.abspath
 
225
        F = abspath
118
226
    [p,e] = os.path.split(f)
119
227
    if e == "" or e == "." or e == "..":
120
228
        return F(f)
121
229
    else:
122
 
        return os.path.join(F(p), e)
123
 
    
 
230
        return pathjoin(F(p), e)
 
231
 
124
232
 
125
233
def backup_file(fn):
126
234
    """Copy a file to a backup.
149
257
    finally:
150
258
        outf.close()
151
259
 
152
 
if os.name == 'nt':
153
 
    import shutil
154
 
    rename = shutil.move
155
 
else:
156
 
    rename = os.rename
157
 
 
158
260
 
159
261
def isdir(f):
160
262
    """True if f is an accessible directory."""
181
283
def is_inside(dir, fname):
182
284
    """True if fname is inside dir.
183
285
    
184
 
    The parameters should typically be passed to os.path.normpath first, so
 
286
    The parameters should typically be passed to osutils.normpath first, so
185
287
    that . and .. and repeated slashes are eliminated, and the separators
186
288
    are canonical for the platform.
187
289
    
188
290
    The empty string as a dir name is taken as top-of-tree and matches 
189
291
    everything.
190
292
    
191
 
    >>> is_inside('src', os.path.join('src', 'foo.c'))
 
293
    >>> is_inside('src', pathjoin('src', 'foo.c'))
192
294
    True
193
295
    >>> is_inside('src', 'srccontrol')
194
296
    False
195
 
    >>> is_inside('src', os.path.join('src', 'a', 'a', 'a', 'foo.c'))
 
297
    >>> is_inside('src', pathjoin('src', 'a', 'a', 'a', 'foo.c'))
196
298
    True
197
299
    >>> is_inside('foo.c', 'foo.c')
198
300
    True
209
311
    if dir == '':
210
312
        return True
211
313
 
212
 
    if dir[-1] != os.sep:
213
 
        dir += os.sep
 
314
    if dir[-1] != '/':
 
315
        dir += '/'
214
316
 
215
317
    return fname.startswith(dir)
216
318
 
226
328
 
227
329
def pumpfile(fromfile, tofile):
228
330
    """Copy contents of one file to another."""
229
 
    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
230
345
 
231
346
 
232
347
def sha_file(f):
289
404
        return -time.timezone
290
405
 
291
406
    
292
 
def format_date(t, offset=0, timezone='original'):
 
407
def format_date(t, offset=0, timezone='original', date_fmt=None, 
 
408
                show_offset=True):
293
409
    ## TODO: Perhaps a global option to use either universal or local time?
294
410
    ## Or perhaps just let people set $TZ?
295
411
    assert isinstance(t, float)
307
423
    else:
308
424
        raise BzrError("unsupported timezone format %r" % timezone,
309
425
                       ['options are "utc", "original", "local"'])
310
 
 
311
 
    return (time.strftime("%a %Y-%m-%d %H:%M:%S", tt)
312
 
            + ' %+03d%02d' % (offset / 3600, (offset / 60) % 60))
 
426
    if date_fmt is None:
 
427
        date_fmt = "%a %Y-%m-%d %H:%M:%S"
 
428
    if show_offset:
 
429
        offset_str = ' %+03d%02d' % (offset / 3600, (offset / 60) % 60)
 
430
    else:
 
431
        offset_str = ''
 
432
    return (time.strftime(date_fmt, tt) +  offset_str)
313
433
 
314
434
 
315
435
def compact_date(when):
321
441
    """Return size of given open file."""
322
442
    return os.fstat(f.fileno())[ST_SIZE]
323
443
 
 
444
 
324
445
# Define rand_bytes based on platform.
325
446
try:
326
447
    # Python 2.4 and later have os.urandom,
343
464
                n -= 1
344
465
            return s
345
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
 
346
481
## TODO: We could later have path objects that remember their list
347
482
## decomposition (might be too tricksy though.)
348
483
 
383
518
    for f in p:
384
519
        if (f == '..') or (f == None) or (f == ''):
385
520
            raise BzrError("sorry, %r not allowed in path" % f)
386
 
    return os.path.join(*p)
 
521
    return pathjoin(*p)
387
522
 
388
523
 
389
524
def appendpath(p1, p2):
390
525
    if p1 == '':
391
526
        return p2
392
527
    else:
393
 
        return os.path.join(p1, p2)
 
528
        return pathjoin(p1, p2)
394
529
    
395
530
 
396
531
def split_lines(s):
438
573
            return True
439
574
    else:
440
575
        return False
 
576
 
 
577
 
 
578
def relpath(base, path):
 
579
    """Return path relative to base, or raise exception.
 
580
 
 
581
    The path may be either an absolute path or a path relative to the
 
582
    current working directory.
 
583
 
 
584
    os.path.commonprefix (python2.4) has a bad bug that it works just
 
585
    on string prefixes, assuming that '/u' is a prefix of '/u2'.  This
 
586
    avoids that problem."""
 
587
    rp = abspath(path)
 
588
 
 
589
    s = []
 
590
    head = rp
 
591
    while len(head) >= len(base):
 
592
        if head == base:
 
593
            break
 
594
        head, tail = os.path.split(head)
 
595
        if tail:
 
596
            s.insert(0, tail)
 
597
    else:
 
598
        # XXX This should raise a NotChildPath exception, as its not tied
 
599
        # to branch anymore.
 
600
        raise PathNotChild(rp, base)
 
601
 
 
602
    if s:
 
603
        return pathjoin(*s)
 
604
    else:
 
605
        return ''
 
606
 
 
607
 
 
608
def safe_unicode(unicode_or_utf8_string):
 
609
    """Coerce unicode_or_utf8_string into unicode.
 
610
 
 
611
    If it is unicode, it is returned.
 
612
    Otherwise it is decoded from utf-8. If a decoding error
 
613
    occurs, it is wrapped as a If the decoding fails, the exception is wrapped 
 
614
    as a BzrBadParameter exception.
 
615
    """
 
616
    if isinstance(unicode_or_utf8_string, unicode):
 
617
        return unicode_or_utf8_string
 
618
    try:
 
619
        return unicode_or_utf8_string.decode('utf8')
 
620
    except UnicodeDecodeError:
 
621
        raise BzrBadParameterNotUnicode(unicode_or_utf8_string)
 
622
 
 
623
 
 
624
def terminal_width():
 
625
    """Return estimated terminal width."""
 
626
 
 
627
    # TODO: Do something smart on Windows?
 
628
 
 
629
    # TODO: Is there anything that gets a better update when the window
 
630
    # is resized while the program is running? We could use the Python termcap
 
631
    # library.
 
632
    try:
 
633
        return int(os.environ['COLUMNS'])
 
634
    except (IndexError, KeyError, ValueError):
 
635
        return 80
 
636
 
 
637
def supports_executable():
 
638
    return sys.platform != "win32"