~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/osutils.py

  • Committer: Martin Pool
  • Date: 2005-07-23 13:52:38 UTC
  • Revision ID: mbp@sourcefrog.net-20050723135238-96b1580de8dff136
doc

Show diffs side-by-side

added added

removed removed

Lines of Context:
19
19
import os, types, re, time, errno, sys
20
20
from stat import S_ISREG, S_ISDIR, S_ISLNK, ST_MODE, ST_SIZE
21
21
 
22
 
from errors import bailout, BzrError
23
 
from trace import mutter
 
22
from bzrlib.errors import BzrError
 
23
from bzrlib.trace import mutter
24
24
import bzrlib
25
25
 
26
26
def make_readonly(filename):
38
38
 
39
39
 
40
40
_QUOTE_RE = re.compile(r'([^a-zA-Z0-9.,:/_~-])')
 
41
 
 
42
_SLASH_RE = re.compile(r'[\\/]+')
 
43
 
41
44
def quotefn(f):
42
 
    """Return shell-quoted filename"""
43
 
    ## We could be a bit more terse by using double-quotes etc
44
 
    f = _QUOTE_RE.sub(r'\\\1', f)
45
 
    if f[0] == '~':
46
 
        f[0:1] = r'\~' 
47
 
    return f
 
45
    """Return a quoted filename filename
 
46
 
 
47
    This previously used backslash quoting, but that works poorly on
 
48
    Windows."""
 
49
    # TODO: I'm not really sure this is the best format either.x
 
50
    if _QUOTE_RE.search(f):
 
51
        return '"' + f + '"'
 
52
    else:
 
53
        return f
48
54
 
49
55
 
50
56
def file_kind(f):
56
62
    elif S_ISLNK(mode):
57
63
        return 'symlink'
58
64
    else:
59
 
        raise BzrError("can't handle file kind with mode %o of %r" % (mode, f)) 
 
65
        raise BzrError("can't handle file kind with mode %o of %r" % (mode, f))
 
66
 
 
67
 
 
68
def kind_marker(kind):
 
69
    if kind == 'file':
 
70
        return ''
 
71
    elif kind == 'directory':
 
72
        return '/'
 
73
    elif kind == 'symlink':
 
74
        return '@'
 
75
    else:
 
76
        raise BzrError('invalid file kind %r' % kind)
 
77
 
 
78
 
 
79
 
 
80
def backup_file(fn):
 
81
    """Copy a file to a backup.
 
82
 
 
83
    Backups are named in GNU-style, with a ~ suffix.
 
84
 
 
85
    If the file is already a backup, it's not copied.
 
86
    """
 
87
    import os
 
88
    if fn[-1] == '~':
 
89
        return
 
90
    bfn = fn + '~'
 
91
 
 
92
    inf = file(fn, 'rb')
 
93
    try:
 
94
        content = inf.read()
 
95
    finally:
 
96
        inf.close()
 
97
    
 
98
    outf = file(bfn, 'wb')
 
99
    try:
 
100
        outf.write(content)
 
101
    finally:
 
102
        outf.close()
 
103
 
 
104
def rename(path_from, path_to):
 
105
    """Basically the same as os.rename() just special for win32"""
 
106
    if sys.platform == 'win32':
 
107
        try:
 
108
            os.remove(path_to)
 
109
        except OSError, e:
 
110
            if e.errno != e.ENOENT:
 
111
                raise
 
112
    os.rename(path_from, path_to)
 
113
 
 
114
 
60
115
 
61
116
 
62
117
 
77
132
        return False
78
133
 
79
134
 
 
135
def is_inside(dir, fname):
 
136
    """True if fname is inside dir.
 
137
    
 
138
    The parameters should typically be passed to os.path.normpath first, so
 
139
    that . and .. and repeated slashes are eliminated, and the separators
 
140
    are canonical for the platform.
 
141
    
 
142
    >>> is_inside('src', 'src/foo.c')
 
143
    True
 
144
    >>> is_inside('src', 'srccontrol')
 
145
    False
 
146
    >>> is_inside('src', 'src/a/a/a/foo.c')
 
147
    True
 
148
    >>> is_inside('foo.c', 'foo.c')
 
149
    True
 
150
    """
 
151
    # XXX: Most callers of this can actually do something smarter by 
 
152
    # looking at the inventory
 
153
    
 
154
    dir = dir.split(os.sep)
 
155
    pl = len(dir)
 
156
    fname = fname.split(os.sep)
 
157
   
 
158
    return (len(fname) >= pl) and (dir == fname[:pl])
 
159
    
 
160
 
 
161
def is_inside_any(dir_list, fname):
 
162
    """True if fname is inside any of given dirs."""
 
163
    # quick scan for perfect match
 
164
    if fname in dir_list:
 
165
        return True
 
166
    
 
167
    for dirname in dir_list:
 
168
        if is_inside(dirname, fname):
 
169
            return True
 
170
    else:
 
171
        return False
 
172
 
 
173
 
80
174
def pumpfile(fromfile, tofile):
81
175
    """Copy contents of one file to another."""
82
176
    tofile.write(fromfile.read())
218
312
    if e:
219
313
        m = _EMAIL_RE.search(e)
220
314
        if not m:
221
 
            bailout("%r doesn't seem to contain a reasonable email address" % e)
 
315
            raise BzrError("%r doesn't seem to contain a reasonable email address" % e)
222
316
        return m.group(0)
223
317
 
224
318
    return _auto_user_id()[1]
266
360
        tt = time.localtime(t)
267
361
        offset = local_time_offset(t)
268
362
    else:
269
 
        bailout("unsupported timezone format %r",
 
363
        raise BzrError("unsupported timezone format %r",
270
364
                ['options are "utc", "original", "local"'])
271
365
 
272
366
    return (time.strftime("%a %Y-%m-%d %H:%M:%S", tt)
315
409
    >>> splitpath('a/../b')
316
410
    Traceback (most recent call last):
317
411
    ...
318
 
    BzrError: ("sorry, '..' not allowed in path", [])
 
412
    BzrError: sorry, '..' not allowed in path
319
413
    """
320
414
    assert isinstance(p, types.StringTypes)
321
415
 
326
420
    rps = []
327
421
    for f in ps:
328
422
        if f == '..':
329
 
            bailout("sorry, %r not allowed in path" % f)
 
423
            raise BzrError("sorry, %r not allowed in path" % f)
330
424
        elif (f == '.') or (f == ''):
331
425
            pass
332
426
        else:
337
431
    assert isinstance(p, list)
338
432
    for f in p:
339
433
        if (f == '..') or (f == None) or (f == ''):
340
 
            bailout("sorry, %r not allowed in path" % f)
 
434
            raise BzrError("sorry, %r not allowed in path" % f)
341
435
    return os.path.join(*p)
342
436
 
343
437
 
352
446
    mutter('external command: %s' % `cmd`)
353
447
    if os.system(cmd):
354
448
        if not ignore_errors:
355
 
            bailout('command failed')
356
 
 
 
449
            raise BzrError('command failed')
 
450
 
 
451
 
 
452
def _read_config_value(name):
 
453
    """Read a config value from the file ~/.bzr.conf/<name>
 
454
    Return None if the file does not exist"""
 
455
    try:
 
456
        f = file(os.path.join(config_dir(), name), "r")
 
457
        return f.read().decode(bzrlib.user_encoding).rstrip("\r\n")
 
458
    except IOError, e:
 
459
        if e.errno == errno.ENOENT:
 
460
            return None
 
461
        raise
 
462
 
 
463
 
 
464
def _get_editor():
 
465
    """Return a sequence of possible editor binaries for the current platform"""
 
466
    e = _read_config_value("editor")
 
467
    if e is not None:
 
468
        yield e
 
469
        
 
470
    if os.name == "windows":
 
471
        yield "notepad.exe"
 
472
    elif os.name == "posix":
 
473
        try:
 
474
            yield os.environ["EDITOR"]
 
475
        except KeyError:
 
476
            yield "/usr/bin/vi"
 
477
 
 
478
 
 
479
def _run_editor(filename):
 
480
    """Try to execute an editor to edit the commit message. Returns True on success,
 
481
    False on failure"""
 
482
    for e in _get_editor():
 
483
        x = os.spawnvp(os.P_WAIT, e, (e, filename))
 
484
        if x == 0:
 
485
            return True
 
486
        elif x == 127:
 
487
            continue
 
488
        else:
 
489
            break
 
490
    raise BzrError("Could not start any editor. Please specify $EDITOR or use ~/.bzr.conf/editor")
 
491
    return False
 
492
                          
 
493
 
 
494
def get_text_message(infotext, ignoreline = "default"):
 
495
    import tempfile
 
496
    
 
497
    if ignoreline == "default":
 
498
        ignoreline = "-- This line and the following will be ignored --"
 
499
        
 
500
    try:
 
501
        tmp_fileno, msgfilename = tempfile.mkstemp()
 
502
        msgfile = os.close(tmp_fileno)
 
503
        if infotext is not None and infotext != "":
 
504
            hasinfo = True
 
505
            msgfile = file(msgfilename, "w")
 
506
            msgfile.write("\n\n%s\n\n%s" % (ignoreline, infotext))
 
507
            msgfile.close()
 
508
        else:
 
509
            hasinfo = False
 
510
 
 
511
        if not _run_editor(msgfilename):
 
512
            return None
 
513
        
 
514
        started = False
 
515
        msg = []
 
516
        lastline, nlines = 0, 0
 
517
        for line in file(msgfilename, "r"):
 
518
            stripped_line = line.strip()
 
519
            # strip empty line before the log message starts
 
520
            if not started:
 
521
                if stripped_line != "":
 
522
                    started = True
 
523
                else:
 
524
                    continue
 
525
            # check for the ignore line only if there
 
526
            # is additional information at the end
 
527
            if hasinfo and stripped_line == ignoreline:
 
528
                break
 
529
            nlines += 1
 
530
            # keep track of the last line that had some content
 
531
            if stripped_line != "":
 
532
                lastline = nlines
 
533
            msg.append(line)
 
534
            
 
535
        if len(msg) == 0:
 
536
            return None
 
537
        # delete empty lines at the end
 
538
        del msg[lastline:]
 
539
        # add a newline at the end, if needed
 
540
        if not msg[-1].endswith("\n"):
 
541
            return "%s%s" % ("".join(msg), "\n")
 
542
        else:
 
543
            return "".join(msg)
 
544
    finally:
 
545
        # delete the msg file in any case
 
546
        try: os.unlink(msgfilename)
 
547
        except IOError: pass