~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/osutils.py

  • Committer: Martin Pool
  • Date: 2005-06-15 04:24:01 UTC
  • Revision ID: mbp@sourcefrog.net-20050615042401-02a29d106bece661
add-bzr-to-baz allows multiple arguments

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 bzrlib.errors import BzrError
23
 
from bzrlib.trace import mutter
 
22
from errors import bailout, BzrError
 
23
from 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
 
 
44
41
def quotefn(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
 
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
54
48
 
55
49
 
56
50
def file_kind(f):
77
71
 
78
72
 
79
73
 
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
 
 
115
 
 
116
 
 
117
 
 
118
74
def isdir(f):
119
75
    """True if f is an accessible directory."""
120
76
    try:
134
90
 
135
91
def is_inside(dir, fname):
136
92
    """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
93
    """
151
 
    # XXX: Most callers of this can actually do something smarter by 
152
 
    # looking at the inventory
153
 
 
154
 
    if dir == fname:
155
 
        return True
156
 
    
157
 
    if dir[-1] != os.sep:
158
 
        dir += os.sep
159
 
    
160
 
    return fname.startswith(dir)
 
94
    return os.path.commonprefix([dir, fname]) == dir
161
95
 
162
96
 
163
97
def is_inside_any(dir_list, fname):
164
98
    """True if fname is inside any of given dirs."""
 
99
    # quick scan for perfect match
 
100
    if fname in dir_list:
 
101
        return True
 
102
    
165
103
    for dirname in dir_list:
166
104
        if is_inside(dirname, fname):
167
105
            return True
310
248
    if e:
311
249
        m = _EMAIL_RE.search(e)
312
250
        if not m:
313
 
            raise BzrError("%r doesn't seem to contain a reasonable email address" % e)
 
251
            bailout("%r doesn't seem to contain a reasonable email address" % e)
314
252
        return m.group(0)
315
253
 
316
254
    return _auto_user_id()[1]
358
296
        tt = time.localtime(t)
359
297
        offset = local_time_offset(t)
360
298
    else:
361
 
        raise BzrError("unsupported timezone format %r",
 
299
        bailout("unsupported timezone format %r",
362
300
                ['options are "utc", "original", "local"'])
363
301
 
364
302
    return (time.strftime("%a %Y-%m-%d %H:%M:%S", tt)
407
345
    >>> splitpath('a/../b')
408
346
    Traceback (most recent call last):
409
347
    ...
410
 
    BzrError: sorry, '..' not allowed in path
 
348
    BzrError: ("sorry, '..' not allowed in path", [])
411
349
    """
412
350
    assert isinstance(p, types.StringTypes)
413
351
 
418
356
    rps = []
419
357
    for f in ps:
420
358
        if f == '..':
421
 
            raise BzrError("sorry, %r not allowed in path" % f)
 
359
            bailout("sorry, %r not allowed in path" % f)
422
360
        elif (f == '.') or (f == ''):
423
361
            pass
424
362
        else:
429
367
    assert isinstance(p, list)
430
368
    for f in p:
431
369
        if (f == '..') or (f == None) or (f == ''):
432
 
            raise BzrError("sorry, %r not allowed in path" % f)
 
370
            bailout("sorry, %r not allowed in path" % f)
433
371
    return os.path.join(*p)
434
372
 
435
373
 
444
382
    mutter('external command: %s' % `cmd`)
445
383
    if os.system(cmd):
446
384
        if not ignore_errors:
447
 
            raise BzrError('command failed')
448
 
 
449
 
 
450
 
def _read_config_value(name):
451
 
    """Read a config value from the file ~/.bzr.conf/<name>
452
 
    Return None if the file does not exist"""
453
 
    try:
454
 
        f = file(os.path.join(config_dir(), name), "r")
455
 
        return f.read().decode(bzrlib.user_encoding).rstrip("\r\n")
456
 
    except IOError, e:
457
 
        if e.errno == errno.ENOENT:
458
 
            return None
459
 
        raise
460
 
 
461
 
 
462
 
def _get_editor():
463
 
    """Return a sequence of possible editor binaries for the current platform"""
464
 
    e = _read_config_value("editor")
465
 
    if e is not None:
466
 
        yield e
467
 
        
468
 
    if os.name == "windows":
469
 
        yield "notepad.exe"
470
 
    elif os.name == "posix":
471
 
        try:
472
 
            yield os.environ["EDITOR"]
473
 
        except KeyError:
474
 
            yield "/usr/bin/vi"
475
 
 
476
 
 
477
 
def _run_editor(filename):
478
 
    """Try to execute an editor to edit the commit message. Returns True on success,
479
 
    False on failure"""
480
 
    for e in _get_editor():
481
 
        x = os.spawnvp(os.P_WAIT, e, (e, filename))
482
 
        if x == 0:
483
 
            return True
484
 
        elif x == 127:
485
 
            continue
486
 
        else:
487
 
            break
488
 
    raise BzrError("Could not start any editor. Please specify $EDITOR or use ~/.bzr.conf/editor")
489
 
    return False
490
 
                          
491
 
 
492
 
def get_text_message(infotext, ignoreline = "default"):
493
 
    import tempfile
494
 
    
495
 
    if ignoreline == "default":
496
 
        ignoreline = "-- This line and the following will be ignored --"
497
 
        
498
 
    try:
499
 
        tmp_fileno, msgfilename = tempfile.mkstemp()
500
 
        msgfile = os.close(tmp_fileno)
501
 
        if infotext is not None and infotext != "":
502
 
            hasinfo = True
503
 
            msgfile = file(msgfilename, "w")
504
 
            msgfile.write("\n\n%s\n\n%s" % (ignoreline, infotext))
505
 
            msgfile.close()
506
 
        else:
507
 
            hasinfo = False
508
 
 
509
 
        if not _run_editor(msgfilename):
510
 
            return None
511
 
        
512
 
        started = False
513
 
        msg = []
514
 
        lastline, nlines = 0, 0
515
 
        for line in file(msgfilename, "r"):
516
 
            stripped_line = line.strip()
517
 
            # strip empty line before the log message starts
518
 
            if not started:
519
 
                if stripped_line != "":
520
 
                    started = True
521
 
                else:
522
 
                    continue
523
 
            # check for the ignore line only if there
524
 
            # is additional information at the end
525
 
            if hasinfo and stripped_line == ignoreline:
526
 
                break
527
 
            nlines += 1
528
 
            # keep track of the last line that had some content
529
 
            if stripped_line != "":
530
 
                lastline = nlines
531
 
            msg.append(line)
532
 
            
533
 
        if len(msg) == 0:
534
 
            return None
535
 
        # delete empty lines at the end
536
 
        del msg[lastline:]
537
 
        # add a newline at the end, if needed
538
 
        if not msg[-1].endswith("\n"):
539
 
            return "%s%s" % ("".join(msg), "\n")
540
 
        else:
541
 
            return "".join(msg)
542
 
    finally:
543
 
        # delete the msg file in any case
544
 
        try: os.unlink(msgfilename)
545
 
        except IOError: pass
 
385
            bailout('command failed')
 
386