~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/osutils.py

  • Committer: Martin Pool
  • Date: 2005-06-28 05:33:40 UTC
  • Revision ID: mbp@sourcefrog.net-20050628053340-ea73b03fbcde9c46
- Remove XMLMixin class in favour of simple pack_xml, unpack_xml functions
  called as needed.  

- Avoid importing xml and ElementTree library unless needed.

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):
39
39
 
40
40
_QUOTE_RE = re.compile(r'([^a-zA-Z0-9.,:/_~-])')
41
41
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
 
42
    """Return a quoted filename filename
 
43
 
 
44
    This previously used backslash quoting, but that works poorly on
 
45
    Windows."""
 
46
    # TODO: I'm not really sure this is the best format either.x
 
47
    if _QUOTE_RE.search(f):
 
48
        return '"' + f + '"'
 
49
    else:
 
50
        return f
48
51
 
49
52
 
50
53
def file_kind(f):
71
74
 
72
75
 
73
76
 
 
77
def backup_file(fn):
 
78
    """Copy a file to a backup.
 
79
 
 
80
    Backups are named in GNU-style, with a ~ suffix.
 
81
 
 
82
    If the file is already a backup, it's not copied.
 
83
    """
 
84
    import os
 
85
    if fn[-1] == '~':
 
86
        return
 
87
    bfn = fn + '~'
 
88
 
 
89
    inf = file(fn, 'rb')
 
90
    try:
 
91
        content = inf.read()
 
92
    finally:
 
93
        inf.close()
 
94
    
 
95
    outf = file(bfn, 'wb')
 
96
    try:
 
97
        outf.write(content)
 
98
    finally:
 
99
        outf.close()
 
100
 
 
101
 
 
102
 
 
103
 
74
104
def isdir(f):
75
105
    """True if f is an accessible directory."""
76
106
    try:
248
278
    if e:
249
279
        m = _EMAIL_RE.search(e)
250
280
        if not m:
251
 
            bailout("%r doesn't seem to contain a reasonable email address" % e)
 
281
            raise BzrError("%r doesn't seem to contain a reasonable email address" % e)
252
282
        return m.group(0)
253
283
 
254
284
    return _auto_user_id()[1]
296
326
        tt = time.localtime(t)
297
327
        offset = local_time_offset(t)
298
328
    else:
299
 
        bailout("unsupported timezone format %r",
 
329
        raise BzrError("unsupported timezone format %r",
300
330
                ['options are "utc", "original", "local"'])
301
331
 
302
332
    return (time.strftime("%a %Y-%m-%d %H:%M:%S", tt)
345
375
    >>> splitpath('a/../b')
346
376
    Traceback (most recent call last):
347
377
    ...
348
 
    BzrError: ("sorry, '..' not allowed in path", [])
 
378
    BzrError: sorry, '..' not allowed in path
349
379
    """
350
380
    assert isinstance(p, types.StringTypes)
351
381
 
356
386
    rps = []
357
387
    for f in ps:
358
388
        if f == '..':
359
 
            bailout("sorry, %r not allowed in path" % f)
 
389
            raise BzrError("sorry, %r not allowed in path" % f)
360
390
        elif (f == '.') or (f == ''):
361
391
            pass
362
392
        else:
367
397
    assert isinstance(p, list)
368
398
    for f in p:
369
399
        if (f == '..') or (f == None) or (f == ''):
370
 
            bailout("sorry, %r not allowed in path" % f)
 
400
            raise BzrError("sorry, %r not allowed in path" % f)
371
401
    return os.path.join(*p)
372
402
 
373
403
 
382
412
    mutter('external command: %s' % `cmd`)
383
413
    if os.system(cmd):
384
414
        if not ignore_errors:
385
 
            bailout('command failed')
386
 
 
 
415
            raise BzrError('command failed')
 
416
 
 
417
 
 
418
def _read_config_value(name):
 
419
    """Read a config value from the file ~/.bzr.conf/<name>
 
420
    Return None if the file does not exist"""
 
421
    try:
 
422
        f = file(os.path.join(config_dir(), name), "r")
 
423
        return f.read().decode(bzrlib.user_encoding).rstrip("\r\n")
 
424
    except IOError, e:
 
425
        if e.errno == errno.ENOENT:
 
426
            return None
 
427
        raise
 
428
 
 
429
 
 
430
def _get_editor():
 
431
    """Return a sequence of possible editor binaries for the current platform"""
 
432
    e = _read_config_value("editor")
 
433
    if e is not None:
 
434
        yield e
 
435
        
 
436
    if os.name == "windows":
 
437
        yield "notepad.exe"
 
438
    elif os.name == "posix":
 
439
        try:
 
440
            yield os.environ["EDITOR"]
 
441
        except KeyError:
 
442
            yield "/usr/bin/vi"
 
443
 
 
444
 
 
445
def _run_editor(filename):
 
446
    """Try to execute an editor to edit the commit message. Returns True on success,
 
447
    False on failure"""
 
448
    for e in _get_editor():
 
449
        x = os.spawnvp(os.P_WAIT, e, (e, filename))
 
450
        if x == 0:
 
451
            return True
 
452
        elif x == 127:
 
453
            continue
 
454
        else:
 
455
            break
 
456
    raise BzrError("Could not start any editor. Please specify $EDITOR or use ~/.bzr.conf/editor")
 
457
    return False
 
458
                          
 
459
 
 
460
def get_text_message(infotext, ignoreline = "default"):
 
461
    import tempfile
 
462
    
 
463
    if ignoreline == "default":
 
464
        ignoreline = "-- This line and the following will be ignored --"
 
465
        
 
466
    try:
 
467
        tmp_fileno, msgfilename = tempfile.mkstemp()
 
468
        msgfile = os.close(tmp_fileno)
 
469
        if infotext is not None and infotext != "":
 
470
            hasinfo = True
 
471
            msgfile = file(msgfilename, "w")
 
472
            msgfile.write("\n\n%s\n\n%s" % (ignoreline, infotext))
 
473
            msgfile.close()
 
474
        else:
 
475
            hasinfo = False
 
476
 
 
477
        if not _run_editor(msgfilename):
 
478
            return None
 
479
        
 
480
        started = False
 
481
        msg = []
 
482
        lastline, nlines = 0, 0
 
483
        for line in file(msgfilename, "r"):
 
484
            stripped_line = line.strip()
 
485
            # strip empty line before the log message starts
 
486
            if not started:
 
487
                if stripped_line != "":
 
488
                    started = True
 
489
                else:
 
490
                    continue
 
491
            # check for the ignore line only if there
 
492
            # is additional information at the end
 
493
            if hasinfo and stripped_line == ignoreline:
 
494
                break
 
495
            nlines += 1
 
496
            # keep track of the last line that had some content
 
497
            if stripped_line != "":
 
498
                lastline = nlines
 
499
            msg.append(line)
 
500
            
 
501
        if len(msg) == 0:
 
502
            return None
 
503
        # delete empty lines at the end
 
504
        del msg[lastline:]
 
505
        # add a newline at the end, if needed
 
506
        if not msg[-1].endswith("\n"):
 
507
            return "%s%s" % ("".join(msg), "\n")
 
508
        else:
 
509
            return "".join(msg)
 
510
    finally:
 
511
        # delete the msg file in any case
 
512
        try: os.unlink(msgfilename)
 
513
        except IOError: pass